fix(site): strip SVN-style Index headers from diffs before parsing (#23179)

This commit is contained in:
Danielle Maywood
2026-03-17 17:57:00 +00:00
committed by GitHub
parent 41d12b8aa3
commit cd163d404b
2 changed files with 110 additions and 6 deletions
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import {
BORDER_BG_STYLE,
buildEditDiff,
@@ -22,6 +22,7 @@ import {
parseArgs,
parseEditFilesArgs,
shortDurationMs,
stripSvnIndexHeaders,
toProviderLabel,
} from "./utils";
@@ -420,6 +421,20 @@ describe("buildWriteFileDiff", () => {
// That's 1 empty-string line, which is still a valid line.
expect(diff).not.toBeNull();
});
it("does not emit console errors from SVN-style Index headers", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
try {
const diff = buildWriteFileDiff(
"src/components/Example.tsx",
"export default function Example() {\n return <div />;\n}\n",
);
expect(diff).not.toBeNull();
expect(spy).not.toHaveBeenCalled();
} finally {
spy.mockRestore();
}
});
});
describe("getWriteFileDiff", () => {
@@ -520,6 +535,26 @@ describe("buildEditDiff", () => {
expect(diff).not.toBeNull();
});
it("does not emit console errors for multi-edit diffs", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
try {
const diff = buildEditDiff(
"/home/coder/coder/site/src/pages/AgentsPage/AgentsSidebar.tsx",
[
{ search: "const a = 1;", replace: "const a = 2;" },
{ search: "const b = 3;", replace: "const b = 4;" },
],
);
expect(diff).not.toBeNull();
// Before the fix, @pierre/diffs logged:
// parseLineType: Invalid firstChar: "I"
// processFile: invalid rawLine: Index: ...
expect(spy).not.toHaveBeenCalled();
} finally {
spy.mockRestore();
}
});
it("strips leading slash from path", () => {
const diff = buildEditDiff("/src/index.ts", [
{ search: "old", replace: "new" },
@@ -570,6 +605,68 @@ describe("buildEditDiff", () => {
});
});
describe("stripSvnIndexHeaders", () => {
it("removes Index: headers from SVN-style patches", () => {
const input = [
"Index: src/file.ts",
"===================================================================",
"--- src/file.ts",
"+++ src/file.ts",
"@@ -1,1 +1,1 @@",
"-old",
"+new",
"",
].join("\n");
const result = stripSvnIndexHeaders(input);
expect(result).not.toContain("Index:");
expect(result).not.toContain("===");
expect(result).toContain("--- src/file.ts");
});
it("handles multiple Index: headers in concatenated patches", () => {
const input = [
"Index: a.ts",
"===================================================================",
"--- a.ts",
"+++ a.ts",
"@@ -1,1 +1,1 @@",
"-old1",
"+new1",
"Index: b.ts",
"===================================================================",
"--- b.ts",
"+++ b.ts",
"@@ -1,1 +1,1 @@",
"-old2",
"+new2",
"",
].join("\n");
const result = stripSvnIndexHeaders(input);
// Both Index headers removed.
expect(result.match(/Index:/g)).toBeNull();
// Diff content preserved.
expect(result).toContain("-old1");
expect(result).toContain("+new2");
});
it("is a no-op for git-style diffs", () => {
const gitDiff = [
"diff --git a/file.ts b/file.ts",
"--- a/file.ts",
"+++ b/file.ts",
"@@ -1,1 +1,1 @@",
"-old",
"+new",
"",
].join("\n");
expect(stripSvnIndexHeaders(gitDiff)).toBe(gitDiff);
});
it("is a no-op for empty strings", () => {
expect(stripSvnIndexHeaders("")).toBe("");
});
});
describe("constants", () => {
it("COLLAPSED_OUTPUT_HEIGHT is 54", () => {
expect(COLLAPSED_OUTPUT_HEIGHT).toBe(54);
+12 -5
View File
@@ -189,6 +189,15 @@ export function getFileViewerOptionsMinimal(isDark: boolean) {
};
}
/**
* Strips SVN-style "Index:" headers that `Diff.createPatch()`
* emits but `@pierre/diffs` does not recognize as file
* boundaries. Left in place they leak into hunk bodies and
* trigger console errors.
*/
export const stripSvnIndexHeaders = (patch: string): string =>
patch.replace(/^Index: .*\n={3,}\n/gm, "");
export const DIFFS_FONT_STYLE = {
"--diffs-font-family": '"Geist Mono Variable", monospace, monospace',
"--diffs-header-font-family": '"Geist Variable", system-ui, sans-serif',
@@ -261,7 +270,7 @@ export const buildWriteFileDiff = (
): FileDiffMetadata | null => {
if (!content) return null;
const patch = Diff.createPatch(path, "", content, "", "");
const parsed = parsePatchFiles(patch);
const parsed = parsePatchFiles(stripSvnIndexHeaders(patch));
if (!parsed.length || !parsed[0].files.length) return null;
return parsed[0].files[0];
};
@@ -338,12 +347,10 @@ export const buildEditDiff = (
// All edits were skipped (empty search). Produce a
// header-only patch so the parser still returns a file
// entry with zero hunks.
patches.push(
`Index: ${diffPath}\n===================================================================\n--- ${diffPath}\n+++ ${diffPath}\n`,
);
patches.push(`--- ${diffPath}\n+++ ${diffPath}\n`);
}
const parsed = parsePatchFiles(patches.join(""));
const parsed = parsePatchFiles(stripSvnIndexHeaders(patches.join("")));
if (!parsed.length || !parsed[0].files.length) return null;
return parsed[0].files[0];
};