diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts index 0efeff544f66..f1952f79a1af 100644 --- a/packages/opencode/src/patch/index.ts +++ b/packages/opencode/src/patch/index.ts @@ -72,30 +72,35 @@ export namespace Patch { } // Parser implementation + const ADD_PREFIX = "*** Add File:" + const DELETE_PREFIX = "*** Delete File:" + const UPDATE_PREFIX = "*** Update File:" + const MOVE_PREFIX = "*** Move to:" + function parsePatchHeader( lines: string[], startIdx: number, ): { filePath: string; movePath?: string; nextIdx: number } | null { const line = lines[startIdx] - if (line.startsWith("*** Add File:")) { - const filePath = line.split(":", 2)[1]?.trim() + if (line.startsWith(ADD_PREFIX)) { + const filePath = line.slice(ADD_PREFIX.length).trim() return filePath ? { filePath, nextIdx: startIdx + 1 } : null } - if (line.startsWith("*** Delete File:")) { - const filePath = line.split(":", 2)[1]?.trim() + if (line.startsWith(DELETE_PREFIX)) { + const filePath = line.slice(DELETE_PREFIX.length).trim() return filePath ? { filePath, nextIdx: startIdx + 1 } : null } - if (line.startsWith("*** Update File:")) { - const filePath = line.split(":", 2)[1]?.trim() + if (line.startsWith(UPDATE_PREFIX)) { + const filePath = line.slice(UPDATE_PREFIX.length).trim() let movePath: string | undefined let nextIdx = startIdx + 1 // Check for move directive - if (nextIdx < lines.length && lines[nextIdx].startsWith("*** Move to:")) { - movePath = lines[nextIdx].split(":", 2)[1]?.trim() + if (nextIdx < lines.length && lines[nextIdx].startsWith(MOVE_PREFIX)) { + movePath = lines[nextIdx].slice(MOVE_PREFIX.length).trim() nextIdx++ } diff --git a/packages/opencode/test/patch/patch.test.ts b/packages/opencode/test/patch/patch.test.ts index 020253bfe2d1..04d40f4894a8 100644 --- a/packages/opencode/test/patch/patch.test.ts +++ b/packages/opencode/test/patch/patch.test.ts @@ -80,6 +80,37 @@ describe("Patch namespace", () => { } }) + test("should parse paths containing colons (Windows drive letters)", () => { + const patchText = `*** Begin Patch +*** Add File: C:\\Users\\user\\project\\new.txt ++content +*** Delete File: D:\\repos\\project\\old.txt +*** Update File: E:\\work\\src\\main.ts +*** Move to: E:\\work\\src\\renamed.ts +@@ +-old ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + expect(result.hunks).toHaveLength(3) + + const add = result.hunks[0] + expect(add.type).toBe("add") + expect(add.path).toBe("C:\\Users\\user\\project\\new.txt") + + const del = result.hunks[1] + expect(del.type).toBe("delete") + expect(del.path).toBe("D:\\repos\\project\\old.txt") + + const update = result.hunks[2] + expect(update.type).toBe("update") + expect(update.path).toBe("E:\\work\\src\\main.ts") + if (update.type === "update") { + expect(update.move_path).toBe("E:\\work\\src\\renamed.ts") + } + }) + test("should throw error for invalid patch format", () => { const invalidPatch = `This is not a valid patch`