mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(site): strip SVN-style Index headers from diffs before parsing (#23179)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user