authorgravatar for 228381532+im-kvijay@users.noreply.github.comKrishna Vijay <228381532+im-kvijay@users.noreply.github.com> 2026-09-15 16:23:24-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-15 23:23:24+00:00
logb12c92f695a6b12116fb2cda40d610bdbe2a9566
treea372d039f0688a23e18e37c13d927c7a2afd2d08
parent37a8ab2b29dfa33b4e20ad709a9860f0da7b7eb2
signature Signed by PGP key B5690EEEBB952194

fix(server): reuse Git index metadata during checkpoint capture (#10792)


2 files changed, 276 insertions(+), 6 deletions(-)

apps/server/src/vcs/GitVcsDriver.test.ts+237
......@@ -65,6 +65,243 @@ runVcsDriverContractSuite<GitVcsDriver.GitVcsDriver, GitContractError>({
6565 },
6666});
6767
68const makeCheckpointFixture = Effect.fn("makeCheckpointFixture")(function* (
69 driver: Effect.Success<ReturnType<typeof GitVcsDriver.makeVcsDriverShape>>,
70 cwd: string,
71) {
72 const fileSystem = yield* FileSystem.FileSystem;
73 const path = yield* Path.Path;
74 const git = (args: ReadonlyArray<string>) =>
75 driver.execute({ operation: "checkpoint-test", cwd, args });
76 yield* git(["init"]);
77 yield* git(["config", "user.name", "Test"]);
78 yield* git(["config", "user.email", "test@test.com"]);
79 yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "initial\n");
80 yield* git(["add", "."]);
81 yield* git(["commit", "-m", "initial"]);
82 const checkpointRef = CheckpointRef.make("refs/t3/checkpoints/test");
83 yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "staged\n");
84 yield* git(["add", "."]);
85 yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "unstaged\n");
86 return { git, checkpointRef };
87});
88
89it.effect("checkpoint capture does not rerun clean filters for unchanged indexed files", () =>
90 Effect.gen(function* () {
91 const fileSystem = yield* FileSystem.FileSystem;
92 const path = yield* Path.Path;
93 const driver = yield* GitVcsDriver.makeVcsDriverShape();
94 const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-cache-" });
95 const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd);
96 yield* fileSystem.writeFileString(
97 path.join(cwd, ".gitattributes"),
98 "stable.txt filter=probe\n",
99 );
100 yield* fileSystem.writeFileString(path.join(cwd, "stable.txt"), "unchanged\n");
101 yield* fileSystem.writeFileString(
102 path.join(cwd, ".git", "filter.cjs"),
103 'require("node:fs").appendFileSync(".git/filter-runs", "read\\n"); process.stdin.pipe(process.stdout);',
104 );
105 yield* git(["config", "filter.probe.clean", "node .git/filter.cjs"]);
106 yield* fileSystem.utimes(path.join(cwd, "stable.txt"), 1_700_000_000, 1_700_000_000);
107 yield* git(["add", "."]);
108 yield* git(["commit", "-m", "record stable file"]);
109 yield* fileSystem.writeFileString(path.join(cwd, ".git", "filter-runs"), "");
110 yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "changed\n");
111 const originalIndex = yield* fileSystem.readFile(path.join(cwd, ".git", "index"));
112
113 yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef });
114
115 assert.strictEqual(yield* fileSystem.readFileString(path.join(cwd, ".git", "filter-runs")), "");
116 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "changed\n");
117 assert.strictEqual((yield* git(["show", `${checkpointRef}:stable.txt`])).stdout, "unchanged\n");
118 assert.deepEqual(yield* fileSystem.readFile(path.join(cwd, ".git", "index")), originalIndex);
119 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
120);
121
122for (const timestamp of [1_700_000_000, 1_700_000_000.9999]) {
123 it.effect(
124 `checkpoint capture preserves same-size edits with racy index timestamps (${timestamp})`,
125 () =>
126 Effect.gen(function* () {
127 const fileSystem = yield* FileSystem.FileSystem;
128 const path = yield* Path.Path;
129 const driver = yield* GitVcsDriver.makeVcsDriverShape();
130 const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-racy-" });
131 const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd);
132 const filePath = path.join(cwd, "file.txt");
133 const indexPath = path.join(cwd, ".git", "index");
134 yield* git(["config", "core.trustctime", "false"]);
135 yield* fileSystem.writeFileString(filePath, "before\n");
136 yield* fileSystem.utimes(filePath, timestamp, timestamp);
137 yield* git(["add", "file.txt"]);
138 yield* git(["commit", "-m", "record racy file"]);
139 yield* fileSystem.utimes(indexPath, timestamp, timestamp);
140 const originalIndex = yield* fileSystem.readFile(indexPath);
141 const originalIndexMtime = (yield* fileSystem.stat(indexPath)).mtime;
142 yield* fileSystem.writeFileString(filePath, "after!\n");
143 yield* fileSystem.utimes(filePath, timestamp, timestamp);
144
145 yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef });
146
147 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "after!\n");
148 assert.deepEqual(yield* fileSystem.readFile(indexPath), originalIndex);
149 assert.deepEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime);
150 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
151 );
152}
153
154it.effect("checkpoint capture preserves racy edits made after resetting the index", () =>
155 Effect.gen(function* () {
156 const fileSystem = yield* FileSystem.FileSystem;
157 const path = yield* Path.Path;
158 const liveProcess = yield* VcsProcess.VcsProcess;
159 const driver = yield* GitVcsDriver.makeVcsDriverShape();
160 const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-racy-reset-" });
161 const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd);
162 const racyPath = path.join(cwd, "racy.txt");
163 const indexPath = path.join(cwd, ".git", "index");
164 const timestamp = 1_700_000_000;
165 yield* git(["config", "core.trustctime", "false"]);
166 yield* fileSystem.writeFileString(racyPath, "before\n");
167 yield* fileSystem.utimes(racyPath, timestamp, timestamp);
168 yield* git(["add", "."]);
169 yield* git(["commit", "-m", "record racy file"]);
170 yield* fileSystem.writeFileString(path.join(cwd, "file.txt"), "staged\n");
171 yield* git(["add", "file.txt"]);
172 yield* fileSystem.utimes(indexPath, timestamp, timestamp);
173 const originalIndex = yield* fileSystem.readFile(indexPath);
174 const originalIndexMtime = (yield* fileSystem.stat(indexPath)).mtime;
175 const captureDriver = yield* GitVcsDriver.makeVcsDriverShape().pipe(
176 Effect.provideService(VcsProcess.VcsProcess, {
177 run: Effect.fn(function* (input: VcsProcess.VcsProcessInput) {
178 const result = yield* liveProcess.run(input);
179 if (input.args.includes("read-tree") && input.args.includes("--reset")) {
180 yield* fileSystem.writeFileString(racyPath, "after!\n").pipe(Effect.orDie);
181 yield* fileSystem.utimes(racyPath, timestamp, timestamp).pipe(Effect.orDie);
182 }
183 return result;
184 }),
185 }),
186 );
187
188 yield* captureDriver.checkpoints.captureCheckpoint({ cwd, checkpointRef });
189
190 assert.strictEqual((yield* git(["show", `${checkpointRef}:racy.txt`])).stdout, "after!\n");
191 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "staged\n");
192 assert.deepEqual(yield* fileSystem.readFile(indexPath), originalIndex);
193 assert.deepEqual((yield* fileSystem.stat(indexPath)).mtime, originalIndexMtime);
194 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
195);
196
197for (const nested of [false, true]) {
198 for (const indexMode of ["normal", "flags", "split"] as const) {
199 it.effect(
200 `checkpoint index reuse preserves two turns (nested=${nested}, index=${indexMode})`,
201 () =>
202 Effect.gen(function* () {
203 const fileSystem = yield* FileSystem.FileSystem;
204 const path = yield* Path.Path;
205 const driver = yield* GitVcsDriver.makeVcsDriverShape();
206 const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-turns-" });
207 const { git } = yield* makeCheckpointFixture(driver, cwd);
208 const write = (name: string, contents: string) =>
209 fileSystem.writeFileString(path.join(cwd, name), contents);
210 yield* fileSystem.makeDirectory(path.join(cwd, "scope"));
211 for (const name of [
212 "scope/staged",
213 "scope/deleted",
214 "scope/assumed",
215 "scope/skipped",
216 "outside",
217 ]) {
218 yield* write(name, "original\n");
219 }
220 yield* git(["add", "."]);
221 yield* git(["commit", "-m", "initial scoped files"]);
222 yield* write("scope/staged", "staged\n");
223 yield* write("scope/new-deleted", "staged then deleted\n");
224 yield* write("outside", "staged outside\n");
225 yield* git(["add", "."]);
226 if (indexMode === "flags") {
227 yield* git(["update-index", "--assume-unchanged", "scope/assumed"]);
228 yield* git(["update-index", "--skip-worktree", "scope/skipped"]);
229 }
230 if (indexMode === "split") {
231 yield* git(["update-index", "--split-index"]);
232 }
233 const originalIndex = yield* fileSystem.readFile(path.join(cwd, ".git", "index"));
234 for (const name of ["scope/staged", "scope/assumed", "scope/skipped", "outside"]) {
235 yield* write(name, "working\n");
236 }
237 yield* write("scope/new", "first\n");
238 yield* fileSystem.remove(path.join(cwd, "scope/deleted"));
239 yield* fileSystem.remove(path.join(cwd, "scope/new-deleted"));
240 const captureCwd = nested ? path.join(cwd, "scope") : cwd;
241 const first = CheckpointRef.make("refs/t3/checkpoints/turns/1");
242 const second = CheckpointRef.make("refs/t3/checkpoints/turns/2");
243 yield* driver.checkpoints.captureCheckpoint({ cwd: captureCwd, checkpointRef: first });
244 for (const name of ["scope/staged", "scope/assumed", "scope/skipped"]) {
245 assert.strictEqual((yield* git(["show", `${first}:${name}`])).stdout, "working\n");
246 }
247 assert.strictEqual(
248 (yield* git(["show", `${first}:outside`])).stdout,
249 nested ? "original\n" : "working\n",
250 );
251 const files = (yield* git(["ls-tree", "-r", "--name-only", first])).stdout.split("\n");
252 assert.notInclude(files, "scope/deleted");
253 assert.notInclude(files, "scope/new-deleted");
254 assert.include(files, "scope/new");
255
256 yield* write("scope/staged", "second\n");
257 yield* fileSystem.remove(path.join(cwd, "scope/new"));
258 yield* write("scope/second", "added in second turn\n");
259 yield* driver.checkpoints.captureCheckpoint({ cwd: captureCwd, checkpointRef: second });
260 assert.strictEqual(
261 (yield* git(["diff", "--name-only", first, second])).stdout,
262 "scope/new\nscope/second\nscope/staged\n",
263 );
264 assert.strictEqual((yield* git(["show", `${second}:scope/staged`])).stdout, "second\n");
265 assert.strictEqual(
266 (yield* git(["show", `${second}:scope/second`])).stdout,
267 "added in second turn\n",
268 );
269 assert.deepEqual(
270 yield* fileSystem.readFile(path.join(cwd, ".git", "index")),
271 originalIndex,
272 );
273 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
274 );
275 }
276}
277
278for (const indexState of ["missing", "invalid"] as const) {
279 it.effect(`checkpoint capture falls back when the user index is ${indexState}`, () =>
280 Effect.gen(function* () {
281 const fileSystem = yield* FileSystem.FileSystem;
282 const path = yield* Path.Path;
283 const driver = yield* GitVcsDriver.makeVcsDriverShape();
284 const cwd = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-checkpoint-index-" });
285 const { git, checkpointRef } = yield* makeCheckpointFixture(driver, cwd);
286 const indexPath = path.join(cwd, ".git", "index");
287 if (indexState === "missing") {
288 yield* fileSystem.remove(indexPath);
289 } else {
290 yield* fileSystem.writeFileString(indexPath, "invalid index");
291 }
292
293 yield* driver.checkpoints.captureCheckpoint({ cwd, checkpointRef });
294
295 assert.strictEqual((yield* git(["show", `${checkpointRef}:file.txt`])).stdout, "unstaged\n");
296 if (indexState === "missing") {
297 assert.isFalse(yield* fileSystem.exists(indexPath));
298 } else {
299 assert.strictEqual(yield* fileSystem.readFileString(indexPath), "invalid index");
300 }
301 }).pipe(Effect.scoped, Effect.provide(GitContractLayer)),
302 );
303}
304
68305it.effect("restores empty checkpoints without changing paths outside the workspace", () =>
69306 Effect.gen(function* () {
70307 const fileSystem = yield* FileSystem.FileSystem;
apps/server/src/vcs/GitVcsDriver.ts+39-6
......@@ -773,12 +773,45 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* (
773773 yield* Effect.gen(function* () {
774774 const headExists = yield* hasHeadCommit(input.cwd);
775775 if (headExists) {
776 yield* execute({
777 operation,
778 cwd: input.cwd,
779 args: ["read-tree", "HEAD"],
780 env: commitEnv,
781 });
776 const reusedIndex = yield* Effect.gen(function* () {
777 const indexPath = yield* execute({
778 operation,
779 cwd: input.cwd,
780 args: ["rev-parse", "--path-format=absolute", "--git-path", "index"],
781 });
782 const { mtime } = yield* fileSystem.stat(indexPath.stdout.trim());
783 if (Option.isNone(mtime)) return false;
784 // Stay below the source timestamp even if Date rounded up, preserving Git's racy check.
785 const indexTime = Math.floor((mtime.value.getTime() - 1) / 1000);
786 if (indexTime <= 0) return false;
787 yield* fileSystem.copyFile(indexPath.stdout.trim(), tempIndexPath);
788 // Retain stat data only where the copied index already matches HEAD.
789 yield* execute({
790 operation,
791 cwd: input.cwd,
792 args: ["-c", "core.fsmonitor=false", "read-tree", "--reset", "HEAD"],
793 env: commitEnv,
794 });
795 // read-tree can rewrite the index, so restore its racy timestamp afterward.
796 yield* fileSystem.utimes(tempIndexPath, indexTime, indexTime);
797 const entries = yield* execute({
798 operation,
799 cwd: input.cwd,
800 args: ["ls-files", "-v"],
801 env: commitEnv,
802 maxOutputBytes: WORKSPACE_FILES_MAX_OUTPUT_BYTES,
803 });
804 // A fresh index must still capture assume-unchanged/skip-worktree files.
805 return !entries.stdoutTruncated && !/^[a-zS] /m.test(entries.stdout);
806 }).pipe(Effect.orElseSucceed(() => false));
807 if (!reusedIndex) {
808 yield* execute({
809 operation,
810 cwd: input.cwd,
811 args: ["read-tree", "HEAD"],
812 env: commitEnv,
813 });
814 }
782815 }
783816
784817 yield* execute({