| ... | @@ -86,139 +86,140 @@ const makeCheckpointStore = Effect.gen(function* () { | ... | @@ -86,139 +86,140 @@ const makeCheckpointStore = Effect.gen(function* () { |
| 86 | Effect.catch(() => Effect.succeed(false)), | 86 | Effect.catch(() => Effect.succeed(false)), |
| 87 | ); | 87 | ); |
| 88 | | 88 | |
| 89 | const captureCheckpoint: CheckpointStoreShape["captureCheckpoint"] = (input) => | 89 | const captureCheckpoint: CheckpointStoreShape["captureCheckpoint"] = Effect.fn( |
| 90 | Effect.gen(function* () { | 90 | "captureCheckpoint", |
| 91 | const operation = "CheckpointStore.captureCheckpoint"; | 91 | )(function* (input) { |
| 92 | | 92 | const operation = "CheckpointStore.captureCheckpoint"; |
| 93 | yield* Effect.acquireUseRelease( | 93 | |
| 94 | fs.makeTempDirectory({ prefix: "t3-fs-checkpoint-" }), | 94 | yield* Effect.acquireUseRelease( |
| 95 | (tempDir) => | 95 | fs.makeTempDirectory({ prefix: "t3-fs-checkpoint-" }), |
| 96 | Effect.gen(function* () { | 96 | Effect.fn("captureCheckpoint.withTempDirectory")(function* (tempDir) { |
| 97 | const tempIndexPath = path.join(tempDir, `index-${randomUUID()}`); | 97 | const tempIndexPath = path.join(tempDir, `index-${randomUUID()}`); |
| 98 | const commitEnv: NodeJS.ProcessEnv = { | 98 | const commitEnv: NodeJS.ProcessEnv = { |
| 99 | ...process.env, | 99 | ...process.env, |
| 100 | GIT_INDEX_FILE: tempIndexPath, | 100 | GIT_INDEX_FILE: tempIndexPath, |
| 101 | GIT_AUTHOR_NAME: "T3 Code", | 101 | GIT_AUTHOR_NAME: "T3 Code", |
| 102 | GIT_AUTHOR_EMAIL: "t3code@users.noreply.github.com", | 102 | GIT_AUTHOR_EMAIL: "t3code@users.noreply.github.com", |
| 103 | GIT_COMMITTER_NAME: "T3 Code", | 103 | GIT_COMMITTER_NAME: "T3 Code", |
| 104 | GIT_COMMITTER_EMAIL: "t3code@users.noreply.github.com", | 104 | GIT_COMMITTER_EMAIL: "t3code@users.noreply.github.com", |
| 105 | }; | 105 | }; |
| 106 | | 106 | |
| 107 | const headExists = yield* hasHeadCommit(input.cwd); | 107 | const headExists = yield* hasHeadCommit(input.cwd); |
| 108 | if (headExists) { | 108 | if (headExists) { |
| 109 | yield* git.execute({ | 109 | yield* git.execute({ |
| 110 | operation, | 110 | operation, |
| 111 | cwd: input.cwd, | 111 | cwd: input.cwd, |
| 112 | args: ["read-tree", "HEAD"], | 112 | args: ["read-tree", "HEAD"], |
| 113 | env: commitEnv, | 113 | env: commitEnv, |
| 114 | }); | 114 | }); |
| 115 | } | 115 | } |
| 116 | | 116 | |
| 117 | yield* git.execute({ | 117 | yield* git.execute({ |
| 118 | operation, | 118 | operation, |
| 119 | cwd: input.cwd, | 119 | cwd: input.cwd, |
| 120 | args: ["add", "-A", "--", "."], | 120 | args: ["add", "-A", "--", "."], |
| 121 | env: commitEnv, | 121 | env: commitEnv, |
| 122 | }); | 122 | }); |
| 123 | | 123 | |
| 124 | const writeTreeResult = yield* git.execute({ | 124 | const writeTreeResult = yield* git.execute({ |
| 125 | operation, | 125 | operation, |
| 126 | cwd: input.cwd, | 126 | cwd: input.cwd, |
| 127 | args: ["write-tree"], | 127 | args: ["write-tree"], |
| 128 | env: commitEnv, | 128 | env: commitEnv, |
| 129 | }); | 129 | }); |
| 130 | const treeOid = writeTreeResult.stdout.trim(); | 130 | const treeOid = writeTreeResult.stdout.trim(); |
| 131 | if (treeOid.length === 0) { | 131 | if (treeOid.length === 0) { |
| 132 | return yield* new GitCommandError({ | 132 | return yield* new GitCommandError({ |
| 133 | operation, | 133 | operation, |
| 134 | command: "git write-tree", | 134 | command: "git write-tree", |
| 135 | cwd: input.cwd, | 135 | cwd: input.cwd, |
| 136 | detail: "git write-tree returned an empty tree oid.", | 136 | detail: "git write-tree returned an empty tree oid.", |
| 137 | }); | 137 | }); |
| 138 | } | 138 | } |
| 139 | | 139 | |
| 140 | const message = `t3 checkpoint ref=${input.checkpointRef}`; | 140 | const message = `t3 checkpoint ref=${input.checkpointRef}`; |
| 141 | const commitTreeResult = yield* git.execute({ | 141 | const commitTreeResult = yield* git.execute({ |
| 142 | operation, | 142 | operation, |
| 143 | cwd: input.cwd, | 143 | cwd: input.cwd, |
| 144 | args: ["commit-tree", treeOid, "-m", message], | 144 | args: ["commit-tree", treeOid, "-m", message], |
| 145 | env: commitEnv, | 145 | env: commitEnv, |
| 146 | }); | 146 | }); |
| 147 | const commitOid = commitTreeResult.stdout.trim(); | 147 | const commitOid = commitTreeResult.stdout.trim(); |
| 148 | if (commitOid.length === 0) { | 148 | if (commitOid.length === 0) { |
| 149 | return yield* new GitCommandError({ | 149 | return yield* new GitCommandError({ |
| 150 | operation, | 150 | operation, |
| 151 | command: "git commit-tree", | 151 | command: "git commit-tree", |
| 152 | cwd: input.cwd, | 152 | cwd: input.cwd, |
| 153 | detail: "git commit-tree returned an empty commit oid.", | 153 | detail: "git commit-tree returned an empty commit oid.", |
| 154 | }); | 154 | }); |
| 155 | } | 155 | } |
| 156 | | 156 | |
| 157 | yield* git.execute({ | 157 | yield* git.execute({ |
| 158 | operation, | 158 | operation, |
| 159 | cwd: input.cwd, | 159 | cwd: input.cwd, |
| 160 | args: ["update-ref", input.checkpointRef, commitOid], | 160 | args: ["update-ref", input.checkpointRef, commitOid], |
| 161 | }); | 161 | }); |
| 162 | }), | 162 | }), |
| 163 | (tempDir) => fs.remove(tempDir, { recursive: true }), | 163 | (tempDir) => fs.remove(tempDir, { recursive: true }), |
| 164 | ).pipe( | 164 | ).pipe( |
| 165 | Effect.catchTags({ | 165 | Effect.catchTags({ |
| 166 | PlatformError: (error) => | 166 | PlatformError: (error) => |
| 167 | Effect.fail( | 167 | Effect.fail( |
| 168 | new CheckpointInvariantError({ | 168 | new CheckpointInvariantError({ |
| 169 | operation: "CheckpointStore.captureCheckpoint", | 169 | operation: "CheckpointStore.captureCheckpoint", |
| 170 | detail: "Failed to capture checkpoint.", | 170 | detail: "Failed to capture checkpoint.", |
| 171 | cause: error, | 171 | cause: error, |
| 172 | }), | 172 | }), |
| 173 | ), | 173 | ), |
| 174 | }), | 174 | }), |
| 175 | ); | 175 | ); |
| 176 | }); | 176 | }); |
| 177 | | 177 | |
| 178 | const hasCheckpointRef: CheckpointStoreShape["hasCheckpointRef"] = (input) => | 178 | const hasCheckpointRef: CheckpointStoreShape["hasCheckpointRef"] = (input) => |
| 179 | resolveCheckpointCommit(input.cwd, input.checkpointRef).pipe( | 179 | resolveCheckpointCommit(input.cwd, input.checkpointRef).pipe( |
| 180 | Effect.map((commit) => commit !== null), | 180 | Effect.map((commit) => commit !== null), |
| 181 | ); | 181 | ); |
| 182 | | 182 | |
| 183 | const restoreCheckpoint: CheckpointStoreShape["restoreCheckpoint"] = (input) => | 183 | const restoreCheckpoint: CheckpointStoreShape["restoreCheckpoint"] = Effect.fn( |
| 184 | Effect.gen(function* () { | 184 | "restoreCheckpoint", |
| 185 | const operation = "CheckpointStore.restoreCheckpoint"; | 185 | )(function* (input) { |
| | 186 | const operation = "CheckpointStore.restoreCheckpoint"; |
| 186 | | 187 | |
| 187 | let commitOid = yield* resolveCheckpointCommit(input.cwd, input.checkpointRef); | 188 | let commitOid = yield* resolveCheckpointCommit(input.cwd, input.checkpointRef); |
| 188 | | 189 | |
| 189 | if (!commitOid && input.fallbackToHead === true) { | 190 | if (!commitOid && input.fallbackToHead === true) { |
| 190 | commitOid = yield* resolveHeadCommit(input.cwd); | 191 | commitOid = yield* resolveHeadCommit(input.cwd); |
| 191 | } | 192 | } |
| 192 | | 193 | |
| 193 | if (!commitOid) { | 194 | if (!commitOid) { |
| 194 | return false; | 195 | return false; |
| 195 | } | 196 | } |
| 196 | | 197 | |
| | 198 | yield* git.execute({ |
| | 199 | operation, |
| | 200 | cwd: input.cwd, |
| | 201 | args: ["restore", "--source", commitOid, "--worktree", "--staged", "--", "."], |
| | 202 | }); |
| | 203 | yield* git.execute({ |
| | 204 | operation, |
| | 205 | cwd: input.cwd, |
| | 206 | args: ["clean", "-fd", "--", "."], |
| | 207 | }); |
| | 208 | |
| | 209 | const headExists = yield* hasHeadCommit(input.cwd); |
| | 210 | if (headExists) { |
| 197 | yield* git.execute({ | 211 | yield* git.execute({ |
| 198 | operation, | 212 | operation, |
| 199 | cwd: input.cwd, | 213 | cwd: input.cwd, |
| 200 | args: ["restore", "--source", commitOid, "--worktree", "--staged", "--", "."], | 214 | args: ["reset", "--quiet", "--", "."], |
| 201 | }); | | |
| 202 | yield* git.execute({ | | |
| 203 | operation, | | |
| 204 | cwd: input.cwd, | | |
| 205 | args: ["clean", "-fd", "--", "."], | | |
| 206 | }); | 215 | }); |
| | 216 | } |
| 207 | | 217 | |
| 208 | const headExists = yield* hasHeadCommit(input.cwd); | 218 | return true; |
| 209 | if (headExists) { | 219 | }); |
| 210 | yield* git.execute({ | | |
| 211 | operation, | | |
| 212 | cwd: input.cwd, | | |
| 213 | args: ["reset", "--quiet", "--", "."], | | |
| 214 | }); | | |
| 215 | } | | |
| 216 | | | |
| 217 | return true; | | |
| 218 | }); | | |
| 219 | | 220 | |
| 220 | const diffCheckpoints: CheckpointStoreShape["diffCheckpoints"] = (input) => | 221 | const diffCheckpoints: CheckpointStoreShape["diffCheckpoints"] = Effect.fn("diffCheckpoints")( |
| 221 | Effect.gen(function* () { | 222 | function* (input) { |
| 222 | const operation = "CheckpointStore.diffCheckpoints"; | 223 | const operation = "CheckpointStore.diffCheckpoints"; |
| 223 | | 224 | |
| 224 | let fromCommitOid = yield* resolveCheckpointCommit(input.cwd, input.fromCheckpointRef); | 225 | let fromCommitOid = yield* resolveCheckpointCommit(input.cwd, input.fromCheckpointRef); |
| ... | @@ -247,24 +248,26 @@ const makeCheckpointStore = Effect.gen(function* () { | ... | @@ -247,24 +248,26 @@ const makeCheckpointStore = Effect.gen(function* () { |
| 247 | }); | 248 | }); |
| 248 | | 249 | |
| 249 | return result.stdout; | 250 | return result.stdout; |
| 250 | }); | 251 | }, |
| 251 | | 252 | ); |
| 252 | const deleteCheckpointRefs: CheckpointStoreShape["deleteCheckpointRefs"] = (input) => | 253 | |
| 253 | Effect.gen(function* () { | 254 | const deleteCheckpointRefs: CheckpointStoreShape["deleteCheckpointRefs"] = Effect.fn( |
| 254 | const operation = "CheckpointStore.deleteCheckpointRefs"; | 255 | "deleteCheckpointRefs", |
| 255 | | 256 | )(function* (input) { |
| 256 | yield* Effect.forEach( | 257 | const operation = "CheckpointStore.deleteCheckpointRefs"; |
| 257 | input.checkpointRefs, | 258 | |
| 258 | (checkpointRef) => | 259 | yield* Effect.forEach( |
| 259 | git.execute({ | 260 | input.checkpointRefs, |
| 260 | operation, | 261 | (checkpointRef) => |
| 261 | cwd: input.cwd, | 262 | git.execute({ |
| 262 | args: ["update-ref", "-d", checkpointRef], | 263 | operation, |
| 263 | allowNonZeroExit: true, | 264 | cwd: input.cwd, |
| 264 | }), | 265 | args: ["update-ref", "-d", checkpointRef], |
| 265 | { discard: true }, | 266 | allowNonZeroExit: true, |
| 266 | ); | 267 | }), |
| 267 | }); | 268 | { discard: true }, |
| | 269 | ); |
| | 270 | }); |
| 268 | | 271 | |
| 269 | return { | 272 | return { |
| 270 | isGitRepository, | 273 | isGitRepository, |