From 96b0ae36a82c7004ec4c558b18bf6aba1dfb6887 Mon Sep 17 00:00:00 2001 From: Simone <185146821+Lucenx9@users.noreply.github.com> Date: Wed, 14 Oct 2026 12:35:28 +0200 Subject: [PATCH] fix(shared): keep quoted Git file names intact --- packages/shared/src/gitPatchPath.test.ts | 8 ++++++++ packages/shared/src/gitPatchPath.ts | 3 ++- .../src/server/diffFileRevisions.test.ts | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/gitPatchPath.test.ts b/packages/shared/src/gitPatchPath.test.ts index c1592a92acf70..813cd06ba899c 100644 --- a/packages/shared/src/gitPatchPath.test.ts +++ b/packages/shared/src/gitPatchPath.test.ts @@ -35,6 +35,7 @@ describe("a name written into a header and read back out", () => { "with space.txt", "café/résumé.ts", "🚀.ts", + "\uFEFFtab\tfile.txt", "tab\tfile.txt", "line\nfile.txt", "carriage\rfile.txt", @@ -66,6 +67,13 @@ describe("a name written into a header and read back out", () => { }); describe("unquoteGitPatchPath", () => { + it.each(['"\\357\\273\\277note.txt"', "\\357\\273\\277note.txt"])( + "preserves an escaped U+FEFF at the start of a filename: %j", + (token) => { + expect(unquoteGitPatchPath(token)).toBe("\uFEFFnote.txt"); + }, + ); + it("leaves a token git saw no need to quote alone", () => { expect(unquoteGitPatchPath("src/app.ts")).toBe("src/app.ts"); expect(unquoteGitPatchPath("with space.txt")).toBe("with space.txt"); diff --git a/packages/shared/src/gitPatchPath.ts b/packages/shared/src/gitPatchPath.ts index 767600c4a4b20..4107c71b4a8ce 100644 --- a/packages/shared/src/gitPatchPath.ts +++ b/packages/shared/src/gitPatchPath.ts @@ -43,7 +43,8 @@ const LOWEST_PRINTABLE = 0x20; const BACKSLASH = 0x5c; const utf8 = new TextEncoder(); -const fromUtf8 = new TextDecoder(); +// A leading U+FEFF is part of the filename, not an encoding marker. +const fromUtf8 = new TextDecoder("utf-8", { ignoreBOM: true }); /** * A name as a patch header can carry it: itself where that is unambiguous, and git's quoted form diff --git a/packages/source-control-core/src/server/diffFileRevisions.test.ts b/packages/source-control-core/src/server/diffFileRevisions.test.ts index cd1790adf8bb5..37e85009858b3 100644 --- a/packages/source-control-core/src/server/diffFileRevisions.test.ts +++ b/packages/source-control-core/src/server/diffFileRevisions.test.ts @@ -72,6 +72,20 @@ describe("parseDiffFileRevisions", () => { assert.deepStrictEqual([...revisions], [["new.ts", "2222222"]]); }); + it("keeps a leading U+FEFF in a quoted rename's revision key", () => { + const revisions = parseDiffFileRevisions( + patchOf( + 'diff --git a/old.txt "b/\\357\\273\\277note.txt"', + "similarity index 100%", + "rename from old.txt", + 'rename to "\\357\\273\\277note.txt"', + "index 2222222..2222222 100644", + ), + ); + + assert.deepStrictEqual([...revisions], [["\uFEFFnote.txt", "2222222"]]); + }); + it("leaves out a file Bitbucket excluded, which it gives no index line for", () => { const revisions = parseDiffFileRevisions( patchOf(