diff --git a/packages/app/src/context/file/path.test.ts b/packages/app/src/context/file/path.test.ts index feef6d466ef4..b82488b909d7 100644 --- a/packages/app/src/context/file/path.test.ts +++ b/packages/app/src/context/file/path.test.ts @@ -27,6 +27,17 @@ describe("file path helpers", () => { expect(stripQueryAndHash("a/b.ts")).toBe("a/b.ts") }) + test("should NOT decode literal %20 in raw Windows path", () => { + const path = createPathHelpers(() => "D:\\First%20Second") + // Input is a raw path, not a file:// URL — %20 is a literal folder name character + expect(path.normalize("D:\\First%20Second\\file.txt")).toBe("file.txt") + }) + + test("should decode %20 in file:// URL path", () => { + const path = createPathHelpers(() => "/home/user/My Documents") + expect(path.normalize("file:///home/user/My%20Documents/file.txt")).toBe("file.txt") + }) + test("unquotes git escaped octal path strings", () => { expect(unquoteGitPath('"a/\\303\\251.txt"')).toBe("a/\u00e9.txt") expect(unquoteGitPath('"plain\\nname"')).toBe("plain\nname") diff --git a/packages/app/src/context/file/path.ts b/packages/app/src/context/file/path.ts index 53f072b6cb26..8ca1fc5b8315 100644 --- a/packages/app/src/context/file/path.ts +++ b/packages/app/src/context/file/path.ts @@ -105,7 +105,14 @@ export function createPathHelpers(scope: () => string) { const normalize = (input: string) => { const root = scope() - let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))) + // Only decode percent-encoding for file:// URLs — raw filesystem paths may + // contain literal % characters (e.g. D:\First%20Second) that must be preserved. + const isUrl = input.startsWith("file://") || input.startsWith("file:\\\\") + let path = unquoteGitPath( + isUrl + ? decodeFilePath(stripQueryAndHash(stripFileProtocol(input))) + : stripQueryAndHash(stripFileProtocol(input)), + ) // Separator-agnostic prefix stripping for Cygwin/native Windows compatibility // Only case-insensitive on Windows (drive letter or UNC paths)