diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 4a5dff870fd20831f8e82d49d2d69e0f3385a2f9..47ca4e1787001f6be4415eea4bd4525352b803d1 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1449,6 +1449,44 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + for (const splitIndex of [false, true]) { + it.effect(`keeps the preceding second cached in review previews (split: ${splitIndex})`, () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + yield* writeTextFile(cwd, ".gitattributes", "stable.txt filter=probe\n"); + yield* writeTextFile(cwd, "stable.txt", "unchanged\n"); + yield* writeTextFile( + cwd, + ".git/filter.cjs", + 'require("node:fs").appendFileSync(".git/filter-runs", "read\\n"); process.stdin.pipe(process.stdout);', + ); + yield* git(cwd, ["config", "filter.probe.clean", "node .git/filter.cjs"]); + yield* fs.utimes(path.join(cwd, "stable.txt"), 1_699_999_999.5, 1_699_999_999.5); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "cache stable file"]); + if (splitIndex) yield* git(cwd, ["update-index", "--split-index"]); + const indexPath = path.join(cwd, ".git", "index"); + yield* fs.utimes(indexPath, 1_700_000_000, 1_700_000_000); + const originalIndex = yield* fs.readFile(indexPath); + const originalMtime = (yield* fs.stat(indexPath)).mtime; + yield* writeTextFile(cwd, ".git/filter-runs", ""); + yield* writeTextFile(cwd, "untracked.txt", "new\n"); + const preview = yield* driver.getReviewDiffPreview({ cwd }); + assert.deepStrictEqual( + preview.sources.find((source) => source.kind === "working-tree")!.files, + [{ path: "untracked.txt", previousPath: null, additions: 1, deletions: 0 }], + ); + assert.strictEqual(yield* fs.readFileString(path.join(cwd, ".git/filter-runs")), ""); + assert.deepStrictEqual(yield* fs.readFile(indexPath), originalIndex); + assert.deepStrictEqual((yield* fs.stat(indexPath)).mtime, originalMtime); + }), + ); + } + for (const [timestamp, splitIndex] of [ [1_700_000_000, false], [1_700_000_000.9999, false], diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index b15cd28b4e927fae2f6eb47825a14775d2c519e6..8ec274a46611ee2b6491aa5785f3578b863338f1 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -2358,9 +2358,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* if (indexExists) { const { mtime } = yield* fileSystem.stat(indexPath); yield* fileSystem.copyFile(indexPath, tempIndexPath); - // A newer copy timestamp hides racily clean edits. Round down before Git reads or rewrites it. + // Node FileSystem.stat truncates bigint timestamps to milliseconds before creating its Date. + // Flooring preserves the source second without making preceding-second files racy. const indexTime = Option.isSome(mtime) - ? Math.max(0, Math.floor((mtime.value.getTime() - 1) / 1000)) + ? Math.max(0, Math.floor(mtime.value.getTime() / 1000)) : 0; yield* fileSystem.utimes(tempIndexPath, indexTime, indexTime); }