authorgravatar for 75276669+Gigioxx@users.noreply.github.comGuillermo Casanova <75276669+Gigioxx@users.noreply.github.com> 2026-09-06 20:37:49-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-06 16:37:49-07:00
log86070cbc7430957e542f5b1d4c6a38359329002d
treed4e514f65f00ad820477d8d8090486e60465b7cb
parent79394154dfe1e6373534995022f53ffdcf293e55
signature Signed by PGP key B5690EEEBB952194

fix(server): skip git status scans while the index is locked (#9845)


2 files changed, 83 insertions(+), 0 deletions(-)

apps/server/src/vcs/GitVcsDriverCore.test.ts+47
...@@ -11,6 +11,7 @@ import * as Layer from "effect/Layer";...@@ -11,6 +11,7 @@ import * as Layer from "effect/Layer";
11import * as Path from "effect/Path";11import * as Path from "effect/Path";
12import * as PlatformError from "effect/PlatformError";12import * as PlatformError from "effect/PlatformError";
13import * as Ref from "effect/Ref";13import * as Ref from "effect/Ref";
14import * as Result from "effect/Result";
14import * as Scope from "effect/Scope";15import * as Scope from "effect/Scope";
15import * as Sink from "effect/Sink";16import * as Sink from "effect/Sink";
16import * as Stream from "effect/Stream";17import * as Stream from "effect/Stream";
...@@ -131,6 +132,51 @@ const initRepoWithCommit = (...@@ -131,6 +132,51 @@ const initRepoWithCommit = (
131 return { initialBranch };132 return { initialBranch };
132 });133 });
133134
135for (const location of ["root", "nested", "worktree"] as const) {
136 it.effect(
137 `skips clean filters while the ${location} index is locked and resumes after unlock`,
138 () =>
139 Effect.gen(function* () {
140 const driver = yield* GitVcsDriver.GitVcsDriver;
141 const fs = yield* FileSystem.FileSystem;
142 const path = yield* Path.Path;
143 const repository = yield* makeTmpDir();
144 yield* initRepoWithCommit(repository);
145 const cwd = location === "worktree" ? yield* makeTmpDir() : repository;
146 if (location === "worktree") {
147 yield* git(repository, ["worktree", "add", "--detach", cwd]);
148 }
149 yield* git(cwd, ["config", "filter.probe.clean", "echo clean >> .filter-runs; cat"]);
150 yield* writeTextFile(cwd, ".gitattributes", "asset.bin filter=probe\n");
151 yield* writeTextFile(cwd, ".gitignore", ".filter-runs\n");
152 yield* writeTextFile(cwd, "asset.bin", "original\n");
153 yield* git(cwd, ["add", "."]);
154 yield* git(cwd, ["commit", "-m", "filtered asset"]);
155 NodeFS.utimesSync(path.join(cwd, "asset.bin"), 1, 1);
156 const runsPath = path.join(cwd, ".filter-runs");
157 yield* fs.remove(runsPath, { force: true });
158 const indexPath = yield* git(cwd, ["rev-parse", "--git-path", "index"]);
159 const lockPath = `${path.resolve(cwd, indexPath)}.lock`;
160 yield* fs.writeFileString(lockPath, "");
161 const statusCwd = location === "nested" ? path.join(cwd, "nested") : cwd;
162 yield* fs.makeDirectory(statusCwd, { recursive: true });
163
164 for (let poll = 0; poll < 3; poll++) {
165 const result = yield* driver.statusDetailsLocal(statusCwd).pipe(Effect.result);
166 assert.isTrue(Result.isFailure(result));
167 if (Result.isFailure(result)) assert.include(result.failure.detail, "index is locked");
168 }
169 assert.isFalse(yield* fs.exists(runsPath));
170 assert.isTrue(yield* fs.exists(lockPath));
171
172 yield* fs.remove(lockPath);
173 const status = yield* driver.statusDetailsLocal(statusCwd);
174 assert.isFalse(status.hasWorkingTreeChanges);
175 assert.include(yield* fs.readFileString(runsPath), "clean");
176 }).pipe(Effect.provide(TestLayer)),
177 );
178}
179
134it.effect("uses stable diagnostics for every parsed non-repository command", () => {180it.effect("uses stable diagnostics for every parsed non-repository command", () => {
135 const commands: Array<{ readonly args: ReadonlyArray<string>; readonly lcAll?: string }> = [];181 const commands: Array<{ readonly args: ReadonlyArray<string>; readonly lcAll?: string }> = [];
136 const spawner = ChildProcessSpawner.make((command) =>182 const spawner = ChildProcessSpawner.make((command) =>
...@@ -163,6 +209,7 @@ it.effect("uses stable diagnostics for every parsed non-repository command", ()...@@ -163,6 +209,7 @@ it.effect("uses stable diagnostics for every parsed non-repository command", ()
163 yield* driver.listRefs({ cwd });209 yield* driver.listRefs({ cwd });
164210
165 assert.deepStrictEqual(commands, [211 assert.deepStrictEqual(commands, [
212 { args: ["rev-parse", "--git-path", "index"], lcAll: "C" },
166 { args: ["status", "--porcelain=2", "--branch"], lcAll: "C" },213 { args: ["status", "--porcelain=2", "--branch"], lcAll: "C" },
167 { args: ["rev-parse", "--abbrev-ref", "HEAD"], lcAll: "C" },214 { args: ["rev-parse", "--abbrev-ref", "HEAD"], lcAll: "C" },
168 { args: ["rev-parse", "--git-common-dir"], lcAll: "C" },215 { args: ["rev-parse", "--git-common-dir"], lcAll: "C" },
apps/server/src/vcs/GitVcsDriverCore.ts+36
...@@ -1584,6 +1584,42 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*...@@ -1584,6 +1584,42 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
1584 });1584 });
15851585
1586 const readStatusDetailsLocal = Effect.fn("readStatusDetailsLocal")(function* (cwd: string) {1586 const readStatusDetailsLocal = Effect.fn("readStatusDetailsLocal")(function* (cwd: string) {
1587 const indexResult = yield* executeGitWithStableDiagnostics(
1588 "GitVcsDriver.statusDetails.indexPath",
1589 cwd,
1590 ["rev-parse", "--git-path", "index"],
1591 { allowNonZeroExit: true },
1592 ).pipe(
1593 Effect.catchTags({
1594 GitCommandError: (error) =>
1595 isMissingGitCwdError(error) ? Effect.succeed(null) : Effect.fail(error),
1596 }),
1597 );
1598 if (indexResult === null) return NON_REPOSITORY_STATUS_DETAILS;
1599 if (indexResult.exitCode === 0) {
1600 const lockPath = `${path.resolve(cwd, indexResult.stdout.trim())}.lock`;
1601 const lockError = new GitCommandError({
1602 operation: "GitVcsDriver.statusDetails.indexPath",
1603 command: "git",
1604 cwd,
1605 detail: "Git index is locked. Status will resume when the index lock is removed.",
1606 });
1607 // Status can succeed while locked, repeatedly running LFS clean filters without caching.
1608 if (
1609 yield* fileSystem.exists(lockPath).pipe(
1610 Effect.mapError(
1611 (cause) =>
1612 new GitCommandError({
1613 ...lockError,
1614 detail: "Failed to check the Git index lock.",
1615 cause,
1616 }),
1617 ),
1618 )
1619 ) {
1620 return yield* lockError;
1621 }
1622 }
1587 const statusResult = yield* executeGitWithStableDiagnostics(1623 const statusResult = yield* executeGitWithStableDiagnostics(
1588 "GitVcsDriver.statusDetails.status",1624 "GitVcsDriver.statusDetails.status",
1589 cwd,1625 cwd,