fix: keep native Windows paths in v2 git patches (#1492)

ACP v2 now says each `git_patch` path, once unquoted, is the same native
absolute path string as the matching `changes` entry
(agentclientprotocol/agent-client-protocol#2650).

The v2 form converted `C:\work\App.ts` to `C:/work/App.ts`, so on
Windows the patch and `changes[].path` named different strings. The v2
form now keeps the native path, which Git quoting writes as
`"C:\\work\\App.ts"`. Git's own form, which the AIR extension uses,
still uses forward slashes and the `a/`/`b/` prefixes.
This commit is contained in:
Ben Brandt
2026-10-30 15:39:21 +01:00
committed by GitHub
parent 1d2b92feae
commit c3428de1bf
2 changed files with 12 additions and 6 deletions

View File

@@ -89,7 +89,7 @@ export type PatchForm = "air" | "v2";
/**
* How a git patch names its file. `git` is git's own form: the leading slash
* dropped and the `a/` and `b/` prefixes added. `absolute` is the form of an
* ACP v2 `git_patch`, whose paths must be absolute: the path itself.
* ACP v2 `git_patch`, whose paths must be absolute: the native path itself.
*/
type GitPatchPaths = "git" | "absolute";
@@ -438,11 +438,11 @@ export function gitPatchText(
}
function gitPatchHeader(filePath: string, change: FileChange, paths: GitPatchPaths): string[] {
// A Windows path gets forward slashes so the header names the same file on
// every platform.
const slashed = filePath.replaceAll("\\", "/");
// git drops one leading slash of an absolute path.
const name = paths === "git" ? slashed.replace(/^\/+/u, "") : slashed;
// git's form uses forward slashes, so the header names the same file on
// every platform, and drops one leading slash of an absolute path. An ACP v2
// patch keeps the native path so that, once unquoted, it equals the `changes`
// entry: `C:\work\App.ts` is written `"C:\\work\\App.ts"`.
const name = paths === "git" ? filePath.replaceAll("\\", "/").replace(/^\/+/u, "") : filePath;
const oldName = quoteGitPath(paths === "git" ? "a/" : "", name);
const newName = quoteGitPath(paths === "git" ? "b/" : "", name);
// git ends a ---/+++ name that contains a space with a tab, for GNU patch.

View File

@@ -466,6 +466,12 @@ describe("ACP v2 diffs", () => {
);
});
it("keeps a Windows path native in a git patch, so it matches the changes entry", () => {
expect(gitPatchText("C:\\work\\App.ts", "update", [hunk], "absolute")).toContain(
'diff --git "C:\\\\work\\\\App.ts" "C:\\\\work\\\\App.ts"\n--- "C:\\\\work\\\\App.ts"\n+++ "C:\\\\work\\\\App.ts"\n',
);
});
it("shows no diff at tool use, where the input holds a snippet or no old text", () => {
const renderer = new AcpToolCallRenderer(v2);
const edit = { file_path: "/work/a.ts", old_string: "old", new_string: "new" };