Show removed SQL comments in Git diffs (#6569)

Hunk content resembling patch headers was discarded before line parsing. Metadata is already ignored before the first hunk, so preserve every added and removed line once a hunk starts.

Closes #6564

Co-authored-by: Yu Han <73627805+Mar-garet@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-11 11:03:15 +02:00
committed by GitHub
co-authored by Yu Han
parent 46b1284177
commit a3f75aa9e4
3 changed files with 115 additions and 12 deletions
@@ -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,
);
@@ -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);
@@ -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);