diff --git a/packages/server/src/server/daemon-e2e/checkout-diff-content.e2e.test.ts b/packages/server/src/server/daemon-e2e/checkout-diff-content.e2e.test.ts new file mode 100644 index 0000000000..f0cd4495e1 --- /dev/null +++ b/packages/server/src/server/daemon-e2e/checkout-diff-content.e2e.test.ts @@ -0,0 +1,87 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { createDaemonTestContext } from "../test-utils/index.js"; + +function git(cwd: string, ...args: string[]): string { + return execFileSync("git", args, { cwd, encoding: "utf8" }); +} + +test.each(["uncommitted", "base", "commit"] as const)( + "%s diff preserves removed SQL comments and their deletion counts", + async (mode) => { + const ctx = await createDaemonTestContext(); + const cwd = mkdtempSync(join(tmpdir(), "checkout-diff-content-")); + + try { + git(cwd, "init", "-b", "main"); + git(cwd, "config", "user.name", "Test"); + git(cwd, "config", "user.email", "test@example.com"); + writeFileSync(join(cwd, "query.sql"), "-- old comment\nSELECT 1;\n"); + writeFileSync(join(cwd, "control.sql"), "SELECT 1;\n"); + git(cwd, "add", "."); + git(cwd, "-c", "commit.gpgsign=false", "commit", "-m", "initial"); + writeFileSync(join(cwd, "query.sql"), "-- new comment\nSELECT 1;\n"); + writeFileSync(join(cwd, "control.sql"), "SELECT 2;\n"); + + expect(git(cwd, "diff", "--numstat", "HEAD", "--", "query.sql").trim()).toBe( + "1\t1\tquery.sql", + ); + + if (mode !== "uncommitted") { + git(cwd, "checkout", "-b", "feature"); + git(cwd, "add", "."); + git(cwd, "-c", "commit.gpgsign=false", "commit", "-m", "edit"); + } + + const files = + mode === "commit" + ? [ + ( + await ctx.client.getCommitFileDiff( + cwd, + git(cwd, "rev-parse", "HEAD").trim(), + "query.sql", + ) + ).file, + ( + await ctx.client.getCommitFileDiff( + cwd, + git(cwd, "rev-parse", "HEAD").trim(), + "control.sql", + ) + ).file, + ] + : (await ctx.client.getCheckoutDiff(cwd, { mode, baseRef: "main" })).files; + + const control = files.find((file) => file?.path === "control.sql"); + expect(control).toMatchObject({ additions: 1, deletions: 1 }); + expect(control?.hunks.flatMap((hunk) => hunk.lines)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "remove", content: "SELECT 1;" }), + expect.objectContaining({ type: "add", content: "SELECT 2;" }), + ]), + ); + + const query = files.find((file) => file?.path === "query.sql"); + expect + .soft(query) + .toMatchObject({ additions: 1, deletions: 1, isNew: false, isDeleted: false }); + expect + .soft(query?.hunks.flatMap((hunk) => hunk.lines)) + .toEqual( + expect.arrayContaining([ + expect.objectContaining({ type: "remove", content: "-- old comment" }), + expect.objectContaining({ type: "add", content: "-- new comment" }), + expect.objectContaining({ type: "context", content: "SELECT 1;" }), + ]), + ); + } finally { + await ctx.cleanup(); + rmSync(cwd, { recursive: true, force: true }); + } + }, + 60_000, +); diff --git a/packages/server/src/server/utils/diff-highlighter.test.ts b/packages/server/src/server/utils/diff-highlighter.test.ts index cc6754c50b..56f77fc706 100644 --- a/packages/server/src/server/utils/diff-highlighter.test.ts +++ b/packages/server/src/server/utils/diff-highlighter.test.ts @@ -190,6 +190,34 @@ index 1234567..abcdefg 100644 `; describe("parseDiff", () => { + it.each(["-- comment", "++ marker"])( + "preserves hunk content resembling patch headers: %s", + (content) => { + const files = parseDiff(`diff --git a/example.txt b/example.txt +index 1234567..abcdefg 100644 +--- a/example.txt ++++ b/example.txt +@@ -1 +1 @@ +-${content} before ++${content} after +`); + + expect(files).toHaveLength(1); + expect(files[0]).toMatchObject({ + path: "example.txt", + isNew: false, + isDeleted: false, + additions: 1, + deletions: 1, + }); + expect(files[0].hunks[0].lines).toEqual([ + { type: "header", content: "@@ -1 +1 @@" }, + { type: "remove", content: `${content} before` }, + { type: "add", content: `${content} after` }, + ]); + }, + ); + it("parses a simple diff with one hunk", () => { const files = parseDiff(SIMPLE_DIFF); diff --git a/packages/server/src/server/utils/diff-highlighter.ts b/packages/server/src/server/utils/diff-highlighter.ts index daa1ebbb3d..97d68d1fcd 100644 --- a/packages/server/src/server/utils/diff-highlighter.ts +++ b/packages/server/src/server/utils/diff-highlighter.ts @@ -79,16 +79,6 @@ function extractPathFromDiffHeader(lines: string[]): string { return "unknown"; } -function isMetadataLine(line: string): boolean { - return ( - line.startsWith("index ") || - line.startsWith("--- ") || - line.startsWith("+++ ") || - line.startsWith("new file mode") || - line.startsWith("deleted file mode") - ); -} - function parseHunkHeader(line: string): DiffHunk | null { const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); if (!hunkMatch) return null; @@ -116,8 +106,6 @@ function parseSectionBody(lines: string[]): ParsedSectionBody { for (let i = 1; i < lines.length; i++) { const line = lines[i]; - if (isMetadataLine(line)) continue; - const newHunk = parseHunkHeader(line); if (newHunk) { if (currentHunk) hunks.push(currentHunk);