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