fix(server): display diffs with custom Git presentation settings (#6536)

Request uncolored patches with explicit a/ and b/ prefixes before parsing. Mnemonic paths do not match checkout entries, and forced ANSI colors hide patch headers and hunks.

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-11 03:00:33 +02:00
committed by GitHub
parent ee0d901efb
commit e61009d5fb
3 changed files with 137 additions and 4 deletions
@@ -0,0 +1,123 @@
import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { getCheckoutDiff, getCommitFileDiff } from "./checkout-git.js";
const repos: string[] = [];
afterEach(() => {
for (const repo of repos.splice(0)) rmSync(repo, { recursive: true, force: true });
});
it.each(["diff.mnemonicPrefix=true", "color.diff=always", "diff.noprefix=true"])(
"preserves batched paths, spaces and literal prefix directories with %s",
async (config) => {
const cwd = createChangedRepo(config);
mkdirSync(join(cwd, "a"));
for (const name of ["a/literal.txt", "with spaces.txt"]) {
writeFileSync(join(cwd, name), "before\n");
}
git(cwd, "add", "a/literal.txt", "with spaces.txt");
git(cwd, "-c", "commit.gpgsign=false", "commit", "-m", "add files");
for (const name of ["a/literal.txt", "with spaces.txt"]) {
writeFileSync(join(cwd, name), "after\n");
}
const result = await getCheckoutDiff(cwd, { mode: "uncommitted", includeStructured: true });
expect(result.structured?.map((file) => file.path)).toEqual([
"a/literal.txt",
"file.txt",
"with spaces.txt",
]);
for (const file of result.structured ?? []) {
expect(file.hunks.flatMap((hunk) => hunk.lines)).toEqual(
expect.arrayContaining([
expect.objectContaining({ type: "remove", content: "before" }),
expect.objectContaining({ type: "add", content: "after" }),
]),
);
}
expect(git(cwd, "config", "--get", config.split("=")[0]).trim()).toBe(config.split("=")[1]);
},
);
function git(cwd: string, ...args: string[]): string {
return execFileSync("git", args, { cwd, encoding: "utf8" });
}
function createChangedRepo(config: string): string {
const cwd = realpathSync.native(mkdtempSync(join(tmpdir(), "checkout-patch-format-")));
repos.push(cwd);
git(cwd, "init", "-b", "main");
git(cwd, "config", "user.name", "Test");
git(cwd, "config", "user.email", "test@example.com");
writeFileSync(join(cwd, "file.txt"), "before\n");
git(cwd, "add", ".");
git(cwd, "-c", "commit.gpgsign=false", "commit", "-m", "initial");
git(cwd, "config", ...config.split("="));
writeFileSync(join(cwd, "file.txt"), "after\n");
return cwd;
}
describe.each(["diff.mnemonicPrefix=true", "color.diff=always"])(
"patch format with %s",
(config) => {
it("returns displayable uncommitted diff lines with their file counts", async () => {
const cwd = createChangedRepo(config);
const result = await getCheckoutDiff(cwd, { mode: "uncommitted", includeStructured: true });
expect(result.structured).toHaveLength(1);
expect(result.structured?.[0]).toMatchObject({
path: "file.txt",
additions: 1,
deletions: 1,
});
expect(result.structured?.[0].hunks.flatMap((hunk) => hunk.lines)).toEqual(
expect.arrayContaining([
expect.objectContaining({ type: "remove", content: "before" }),
expect.objectContaining({ type: "add", content: "after" }),
]),
);
});
it("returns displayable committed changes against the base branch", async () => {
const cwd = createChangedRepo(config);
git(cwd, "checkout", "-b", "feature");
git(cwd, "add", ".");
git(cwd, "-c", "commit.gpgsign=false", "commit", "-m", "edit");
const result = await getCheckoutDiff(cwd, {
mode: "base",
baseRef: "main",
includeStructured: true,
});
expect(result.structured?.[0]).toMatchObject({
path: "file.txt",
additions: 1,
deletions: 1,
});
expect(result.structured?.[0].hunks.flatMap((hunk) => hunk.lines)).toEqual(
expect.arrayContaining([expect.objectContaining({ type: "add", content: "after" })]),
);
});
it("returns displayable lines when opening an individual commit file", async () => {
const cwd = createChangedRepo(config);
git(cwd, "add", ".");
git(cwd, "-c", "commit.gpgsign=false", "commit", "-m", "edit");
const file = await getCommitFileDiff({
cwd,
sha: git(cwd, "rev-parse", "HEAD").trim(),
path: "file.txt",
});
expect(file).toMatchObject({ path: "file.txt", additions: 1, deletions: 1 });
expect(file?.hunks.flatMap((hunk) => hunk.lines)).toEqual(
expect.arrayContaining([expect.objectContaining({ type: "add", content: "after" })]),
);
});
},
);
@@ -1507,8 +1507,10 @@ const x = 1;
expect(diff.diff).toContain("# generated.js: diff too large omitted");
expect(diff.diff).toContain(`-export const value = "old";`);
expect(diff.diff).toContain(`+export const value = "new";`);
expect(commands).toContain("diff --numstat HEAD");
expect(commands).toContain("diff HEAD -- :(literal)generated.js :(literal)small.ts");
expect(commands).toContain("diff --no-color --src-prefix=a/ --dst-prefix=b/ --numstat HEAD");
expect(commands).toContain(
"diff --no-color --src-prefix=a/ --dst-prefix=b/ HEAD -- :(literal)generated.js :(literal)small.ts",
);
expect(metrics.maxConcurrent).toBeLessThanOrEqual(8);
});
+10 -2
View File
@@ -636,8 +636,16 @@ function normalizeNumstatPath(pathField: string): string {
return pathField;
}
// Parsed patches need a stable wire format regardless of terminal presentation settings.
const GIT_PATCH_FORMAT_ARGS = ["--no-color", "--src-prefix=a/", "--dst-prefix=b/"];
function buildGitDiffArgs(args: { ignoreWhitespace?: boolean; extra: string[] }): string[] {
return ["diff", ...(args.ignoreWhitespace ? ["-w"] : []), ...args.extra];
return [
"diff",
...GIT_PATCH_FORMAT_ARGS,
...(args.ignoreWhitespace ? ["-w"] : []),
...args.extra,
];
}
const TRACKED_DIFF_NUMSTAT_MAX_BYTES = 2 * 1024 * 1024; // 2MB
@@ -2635,7 +2643,7 @@ export async function getCommitFileDiff({
path: string;
}): Promise<ParsedDiffFile | null> {
const { stdout } = await runGitCommand(
["show", sha, "--format=", "--diff-merges=first-parent", "--", path],
["show", sha, "--format=", "--diff-merges=first-parent", ...GIT_PATCH_FORMAT_ARGS, "--", path],
{
cwd,
envOverlay: READ_ONLY_GIT_ENV,