From 0ac42fc27cfa80b598bb6e05da2fb5b4b2dbba1b Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Sun, 12 Jul 2026 12:35:59 -0400 Subject: [PATCH 1/4] Detect file renames in review diffs --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 111 ++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 127 ++++++++++--------- 2 files changed, 178 insertions(+), 60 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index dc58fc2543c..fe107e3f1f5 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -209,6 +209,21 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + it.effect("includes untracked files before the repository has its first commit", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* driver.initRepo({ cwd }); + yield* writeTextFile(cwd, "first.txt", "first\n"); + + const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false }); + const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? ""; + + assert.include(diff, "diff --git a/first.txt b/first.txt"); + assert.include(diff, "+first"); + }), + ); + it.effect("honors whitespace filtering for worktree and branch previews", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); @@ -243,6 +258,102 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { ); }), ); + + it.effect("detects an unstaged rename with edits as one working-tree diff", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const fileSystem = yield* FileSystem.FileSystem; + const pathService = yield* Path.Path; + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* writeTextFile( + cwd, + "before.ts", + [ + "export const first = 1;", + "export const second = 2;", + "export const third = 3;", + "export const fourth = 4;", + "", + ].join("\n"), + ); + yield* git(cwd, ["add", "before.ts"]); + yield* git(cwd, ["commit", "-m", "add source file"]); + yield* fileSystem.rename( + pathService.join(cwd, "before.ts"), + pathService.join(cwd, "after.ts"), + ); + yield* writeTextFile( + cwd, + "after.ts", + [ + "export const first = 1;", + "export const second = 2;", + "export const third = 30;", + "export const fourth = 4;", + "", + ].join("\n"), + ); + + const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false }); + const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? ""; + + assert.include(diff, "rename from before.ts"); + assert.include(diff, "rename to after.ts"); + assert.include(diff, "-export const third = 3;"); + assert.include(diff, "+export const third = 30;"); + assert.notInclude(diff, "--- /dev/null"); + assert.notInclude(diff, "+++ /dev/null"); + }), + ); + + it.effect("detects a committed rename with edits in the branch diff", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const { initialBranch } = yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* writeTextFile( + cwd, + "before.ts", + [ + "export const first = 1;", + "export const second = 2;", + "export const third = 3;", + "export const fourth = 4;", + "", + ].join("\n"), + ); + yield* git(cwd, ["add", "before.ts"]); + yield* git(cwd, ["commit", "-m", "add source file"]); + yield* git(cwd, ["checkout", "-b", "feature/rename"]); + yield* git(cwd, ["mv", "before.ts", "after.ts"]); + yield* writeTextFile( + cwd, + "after.ts", + [ + "export const first = 1;", + "export const second = 2;", + "export const third = 30;", + "export const fourth = 4;", + "", + ].join("\n"), + ); + yield* git(cwd, ["add", "after.ts"]); + yield* git(cwd, ["commit", "-m", "rename source file"]); + + const preview = yield* driver.getReviewDiffPreview({ + cwd, + baseRef: initialBranch, + ignoreWhitespace: false, + }); + const diff = preview.sources.find((source) => source.kind === "branch-range")?.diff ?? ""; + + assert.include(diff, "rename from before.ts"); + assert.include(diff, "rename to after.ts"); + assert.include(diff, "-export const third = 3;"); + assert.include(diff, "+export const third = 30;"); + }), + ); }); describe("repository status", () => { diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index a406cbce549..4d6c65f8ce0 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -45,8 +45,6 @@ const RANGE_COMMIT_SUMMARY_MAX_OUTPUT_BYTES = 19_000; const RANGE_DIFF_SUMMARY_MAX_OUTPUT_BYTES = 19_000; const RANGE_DIFF_PATCH_MAX_OUTPUT_BYTES = 59_000; const REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES = 120_000; -const REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES = 80_000; -const WORKSPACE_FILES_MAX_OUTPUT_BYTES = 120_000; const STATUS_UPSTREAM_REFRESH_INTERVAL = Duration.seconds(15); const STATUS_UPSTREAM_REFRESH_TIMEOUT = Duration.seconds(5); @@ -1807,43 +1805,71 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* }; }); - const readUntrackedReviewDiffs = Effect.fn("readUntrackedReviewDiffs")(function* (cwd: string) { - const untrackedResult = yield* executeGit( - "GitVcsDriver.readUntrackedReviewDiffs.list", - cwd, - ["ls-files", "--others", "--exclude-standard", "-z"], - { - maxOutputBytes: WORKSPACE_FILES_MAX_OUTPUT_BYTES, - appendTruncationMarker: true, - }, - ); - const untrackedPaths = splitNullSeparatedGitStdoutPaths(untrackedResult); - if (untrackedPaths.length === 0) { - return { diff: "", truncated: untrackedResult.stdoutTruncated }; - } - - const diffs = yield* Effect.forEach( - untrackedPaths, - (relativePath) => - executeGit( - "GitVcsDriver.readUntrackedReviewDiffs.diff", - cwd, - ["diff", "--no-index", "--patch", "--minimal", "--", "/dev/null", relativePath], - { - allowNonZeroExit: true, - maxOutputBytes: REVIEW_UNTRACKED_DIFF_MAX_OUTPUT_BYTES, - appendTruncationMarker: true, - }, + const readWorkingTreeReviewDiff = Effect.fn("readWorkingTreeReviewDiff")(function* ( + cwd: string, + ignoreWhitespace: boolean | undefined, + ) { + const tempDirectory = yield* fileSystem + .makeTempDirectory({ prefix: "t3code-review-index-" }) + .pipe( + Effect.mapError( + (cause) => + new GitCommandError({ + operation: "GitVcsDriver.readWorkingTreeReviewDiff.createTempIndex", + command: "git diff", + cwd, + detail: "Failed to create a temporary Git index for the review diff.", + cause, + }), ), - { concurrency: 4 }, - ); + ); + const indexPath = path.join(tempDirectory, "index"); + const env = { GIT_INDEX_FILE: indexPath } satisfies NodeJS.ProcessEnv; - return { - diff: Arr.filterMap(diffs, (result) => - result.stdout.trim().length > 0 ? Result.succeed(result.stdout) : Result.failVoid, - ).join("\n"), - truncated: untrackedResult.stdoutTruncated || diffs.some((result) => result.stdoutTruncated), - }; + return yield* Effect.gen(function* () { + const headResult = yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.resolveHead", + cwd, + ["rev-parse", "--verify", "--quiet", "HEAD"], + { allowNonZeroExit: true }, + ); + const hasHead = headResult.exitCode === 0; + yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.readTree", + cwd, + hasHead ? ["read-tree", "HEAD"] : ["read-tree", "--empty"], + { env }, + ); + yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.add", + cwd, + ["add", "--intent-to-add", "-A", "--", "."], + { env }, + ); + + return yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.diff", + cwd, + [ + "diff", + "--patch", + "--minimal", + "--find-renames", + ...(ignoreWhitespace ? ["--ignore-all-space"] : []), + ...(hasHead ? ["HEAD"] : []), + "--", + ], + { + env, + maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES, + appendTruncationMarker: true, + }, + ); + }).pipe( + Effect.ensuring( + fileSystem.remove(tempDirectory, { recursive: true, force: true }).pipe(Effect.ignore), + ), + ); }); const getReviewDiffPreview = Effect.fn("getReviewDiffPreview")(function* ( @@ -1867,22 +1893,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* ) : null); - const dirtyTrackedResult = yield* executeGit( - "GitVcsDriver.getReviewDiffPreview.dirtyTracked", - input.cwd, - [ - "diff", - "--patch", - "--minimal", - ...(input.ignoreWhitespace ? ["--ignore-all-space"] : []), - "HEAD", - "--", - ], - { - maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES, - appendTruncationMarker: true, - }, - ).pipe( + const dirtyResult = yield* readWorkingTreeReviewDiff(input.cwd, input.ignoreWhitespace).pipe( Effect.orElseSucceed(() => ({ exitCode: 0, stdout: "", @@ -1891,12 +1902,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* stderrTruncated: false, })), ); - const dirtyUntracked = yield* readUntrackedReviewDiffs(input.cwd).pipe( - Effect.orElseSucceed(() => ({ diff: "", truncated: false })), - ); - const dirtyDiff = [dirtyTrackedResult.stdout.trimEnd(), dirtyUntracked.diff.trimEnd()] - .filter((diff) => diff.length > 0) - .join("\n"); + const dirtyDiff = dirtyResult.stdout; const baseResult = baseRef && branch @@ -1907,6 +1913,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* "diff", "--patch", "--minimal", + "--find-renames", ...(input.ignoreWhitespace ? ["--ignore-all-space"] : []), `${baseRef}...HEAD`, ], @@ -1953,7 +1960,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* headRef: null, diff: dirtyDiff, diffHash: dirtyDiffHash, - truncated: dirtyTrackedResult.stdoutTruncated || dirtyUntracked.truncated, + truncated: dirtyResult.stdoutTruncated, }, { id: "branch-range", From cd85e36de53d7be8f6da185723eae4c1cf230e81 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Sun, 12 Jul 2026 13:22:50 -0400 Subject: [PATCH 2/4] Preserve sparse checkout metadata in review diffs --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 21 +++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 32 +++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index fe107e3f1f5..7f501e3e280 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -307,6 +307,27 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + it.effect("preserves sparse-checkout entries in the working-tree diff", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* writeTextFile(cwd, "included/file.ts", "export const included = 1;\n"); + yield* writeTextFile(cwd, "excluded/file.ts", "export const excluded = 1;\n"); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "add sparse files"]); + yield* git(cwd, ["sparse-checkout", "set", "included"]); + yield* writeTextFile(cwd, "included/file.ts", "export const included = 2;\n"); + + const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false }); + const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? ""; + + assert.include(diff, "diff --git a/included/file.ts b/included/file.ts"); + assert.include(diff, "+export const included = 2;"); + assert.notInclude(diff, "excluded/file.ts"); + }), + ); + it.effect("detects a committed rename with edits in the branch diff", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 4d6c65f8ce0..7da1b400535 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1834,12 +1834,36 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* { allowNonZeroExit: true }, ); const hasHead = headResult.exitCode === 0; - yield* executeGit( - "GitVcsDriver.readWorkingTreeReviewDiff.readTree", + const gitIndexResult = yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.resolveIndex", cwd, - hasHead ? ["read-tree", "HEAD"] : ["read-tree", "--empty"], - { env }, + ["rev-parse", "--git-path", "index"], ); + const gitIndexPath = path.isAbsolute(gitIndexResult.stdout.trim()) + ? gitIndexResult.stdout.trim() + : path.resolve(cwd, gitIndexResult.stdout.trim()); + + if (yield* fileSystem.exists(gitIndexPath)) { + yield* fileSystem.copyFile(gitIndexPath, indexPath).pipe( + Effect.mapError( + (cause) => + new GitCommandError({ + operation: "GitVcsDriver.readWorkingTreeReviewDiff.copyIndex", + command: "git diff", + cwd, + detail: "Failed to copy the Git index for the review diff.", + cause, + }), + ), + ); + } else { + yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.readEmptyTree", + cwd, + ["read-tree", "--empty"], + { env }, + ); + } yield* executeGit( "GitVcsDriver.readWorkingTreeReviewDiff.add", cwd, From 56ae8a6cba242d4bd2684826643dd2e3114996fc Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Sun, 12 Jul 2026 13:23:26 -0400 Subject: [PATCH 3/4] Cover staged index-only review changes --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 7f501e3e280..5f2f06db4a2 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -328,6 +328,26 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + it.effect("includes staged index-only deletions in the working-tree diff", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* writeTextFile(cwd, ".gitignore", "secret.env\n"); + yield* writeTextFile(cwd, "secret.env", "SECRET=value\n"); + yield* git(cwd, ["add", "-f", ".gitignore", "secret.env"]); + yield* git(cwd, ["commit", "-m", "add ignored tracked file"]); + yield* git(cwd, ["rm", "--cached", "secret.env"]); + + const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false }); + const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? ""; + + assert.include(diff, "diff --git a/secret.env b/secret.env"); + assert.include(diff, "deleted file mode"); + assert.include(diff, "-SECRET=value"); + }), + ); + it.effect("detects a committed rename with edits in the branch diff", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); From aa8d932704f0ae10bde956d8c91a4edff6d76979 Mon Sep 17 00:00:00 2001 From: Jake Leventhal Date: Sun, 12 Jul 2026 13:26:12 -0400 Subject: [PATCH 4/4] Include staged files in unborn review diffs --- apps/server/src/vcs/GitVcsDriverCore.test.ts | 16 ++++++++++++++++ apps/server/src/vcs/GitVcsDriverCore.ts | 10 +++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 5f2f06db4a2..71694e2bcc8 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -224,6 +224,22 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + it.effect("includes staged files before the repository has its first commit", () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + const driver = yield* GitVcsDriver.GitVcsDriver; + yield* driver.initRepo({ cwd }); + yield* writeTextFile(cwd, "staged.txt", "staged\n"); + yield* git(cwd, ["add", "staged.txt"]); + + const preview = yield* driver.getReviewDiffPreview({ cwd, ignoreWhitespace: false }); + const diff = preview.sources.find((source) => source.kind === "working-tree")?.diff ?? ""; + + assert.include(diff, "diff --git a/staged.txt b/staged.txt"); + assert.include(diff, "+staged"); + }), + ); + it.effect("honors whitespace filtering for worktree and branch previews", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 7da1b400535..50cfa391872 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -1834,6 +1834,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* { allowNonZeroExit: true }, ); const hasHead = headResult.exitCode === 0; + const baseline = hasHead + ? "HEAD" + : (yield* executeGit( + "GitVcsDriver.readWorkingTreeReviewDiff.resolveEmptyTree", + cwd, + ["hash-object", "-t", "tree", "--stdin"], + { stdin: "" }, + )).stdout.trim(); const gitIndexResult = yield* executeGit( "GitVcsDriver.readWorkingTreeReviewDiff.resolveIndex", cwd, @@ -1880,7 +1888,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* "--minimal", "--find-renames", ...(ignoreWhitespace ? ["--ignore-all-space"] : []), - ...(hasHead ? ["HEAD"] : []), + baseline, "--", ], {