diff --git a/packages/server/src/server/diff/git.test.ts b/packages/server/src/server/diff/git.test.ts index 3bfd447d05..ebea9003f7 100644 --- a/packages/server/src/server/diff/git.test.ts +++ b/packages/server/src/server/diff/git.test.ts @@ -519,3 +519,42 @@ test.skipIf(process.platform === "win32")( } }, ); + +test.skipIf(process.platform === "win32")( + "highlighted symlink replacements use stored link text instead of target contents", + async () => { + const { encodeDiffFrame, decodeDiffFrame } = + await import("@getpaseo/protocol/binary-frames/index"); + const cwd = await mkdtemp(join(tmpdir(), "diff-highlighted-link-")); + const git = (...args: string[]) => execFileSync("git", args, { cwd, encoding: "utf8" }); + const { symlink, unlink } = await import("node:fs/promises"); + try { + git("init", "-b", "main"); + git("config", "user.name", "Test"); + git("config", "user.email", "test@example.com"); + await writeFile(join(cwd, "file.ts"), "export const old = 1;\n"); + await writeFile(join(cwd, "target.ts"), "export const entirelyDifferentLength = 2;\n"); + git("add", "."); + git("-c", "commit.gpgsign=false", "commit", "-m", "base"); + await unlink(join(cwd, "file.ts")); + await symlink("target.ts", join(cwd, "file.ts")); + const comparison = await listComparison({ + cwd, + base: { kind: "rev", rev: "HEAD" }, + head: { kind: "worktree" }, + }); + try { + const record = await readBody(comparison); + expect(record.body.hunks.flatMap((hunk) => hunk.lines)).toContainEqual( + expect.objectContaining({ type: "add", content: "target.ts" }), + ); + const frame = { opcode: 0x41 as const, slot: 0, revision: 1, files: [record.body] }; + expect(decodeDiffFrame(encodeDiffFrame(frame))).toEqual(frame); + } finally { + await comparison.close(); + } + } finally { + await rm(cwd, { recursive: true, force: true }); + } + }, +); diff --git a/packages/server/src/server/diff/sources.ts b/packages/server/src/server/diff/sources.ts index 01aacf4c82..ab47ef2260 100644 --- a/packages/server/src/server/diff/sources.ts +++ b/packages/server/src/server/diff/sources.ts @@ -1,3 +1,5 @@ +import { lstat, readlink } from "node:fs/promises"; +import { resolve } from "node:path"; import { readGitFileContents, readGitFileContentAtRef } from "../../utils/git-file-contents.js"; import type { ListedFile } from "./git.js"; @@ -34,8 +36,12 @@ export class HighlightSources { ? null : await this.content(contents, this.options.baseSha, file.entry.oldPath ?? file.entry.path); let newFileContent: string | null | undefined; - if (this.options.headSha !== null) + if (this.options.headSha !== null) { newFileContent = await this.content(contents, this.options.headSha, file.entry.path); + } else if (file.entry.change !== "deleted") { + const path = resolve(this.options.cwd, file.entry.path); + if ((await lstat(path)).isSymbolicLink()) newFileContent = await readlink(path); + } return { oldFileContent, newFileContent }; } private async load(start: number): Promise | null> {