diff --git a/packages/opencode/src/patch/index.ts b/packages/opencode/src/patch/index.ts index 0efeff544f66..140a03ebb4d2 100644 --- a/packages/opencode/src/patch/index.ts +++ b/packages/opencode/src/patch/index.ts @@ -78,28 +78,26 @@ export namespace Patch { ): { filePath: string; movePath?: string; nextIdx: number } | null { const line = lines[startIdx] - if (line.startsWith("*** Add File:")) { - const filePath = line.split(":", 2)[1]?.trim() - return filePath ? { filePath, nextIdx: startIdx + 1 } : null - } + const valueAfter = (prefix: string) => (line.startsWith(prefix) ? line.slice(prefix.length).trim() : undefined) - if (line.startsWith("*** Delete File:")) { - const filePath = line.split(":", 2)[1]?.trim() - return filePath ? { filePath, nextIdx: startIdx + 1 } : null - } + const addPath = valueAfter("*** Add File:") + if (addPath !== undefined) return addPath ? { filePath: addPath, nextIdx: startIdx + 1 } : null + + const deletePath = valueAfter("*** Delete File:") + if (deletePath !== undefined) return deletePath ? { filePath: deletePath, nextIdx: startIdx + 1 } : null - if (line.startsWith("*** Update File:")) { - const filePath = line.split(":", 2)[1]?.trim() + const updatePath = valueAfter("*** Update File:") + if (updatePath !== undefined) { 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() + movePath = lines[nextIdx].slice("*** Move to:".length).trim() || undefined nextIdx++ } - return filePath ? { filePath, movePath, nextIdx } : null + return updatePath ? { filePath: updatePath, movePath, nextIdx } : null } return null diff --git a/packages/opencode/test/patch/patch.test.ts b/packages/opencode/test/patch/patch.test.ts index 020253bfe2d1..69a809d674b4 100644 --- a/packages/opencode/test/patch/patch.test.ts +++ b/packages/opencode/test/patch/patch.test.ts @@ -80,6 +80,113 @@ describe("Patch namespace", () => { } }) + test("should parse Windows drive-letter paths", () => { + const winPath = String.raw`D:\Root\twinUpgrade\simulationPython\frontend\src\views\ScenarioView.vue` + const patchText = `*** Begin Patch +*** Update File: ${winPath} +@@ +-old ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + expect(result.hunks).toHaveLength(1) + const hunk = result.hunks[0] + expect(hunk.type).toBe("update") + expect(hunk.path).toBe(winPath) + }) + + test("should parse Windows drive-letter move paths", () => { + const src = String.raw`D:\Root\from.txt` + const dst = String.raw`D:\Root\to.txt` + const patchText = `*** Begin Patch +*** Update File: ${src} +*** Move to: ${dst} +@@ +-old ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + expect(result.hunks).toHaveLength(1) + const hunk = result.hunks[0] + expect(hunk.type).toBe("update") + expect(hunk.path).toBe(src) + if (hunk.type === "update") { + expect(hunk.move_path).toBe(dst) + } + }) + + test("should parse Windows UNC paths", () => { + const uncPath = String.raw`\\server\share\path\file.txt` + const patchText = `*** Begin Patch +*** Update File: ${uncPath} +@@ +-old ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + const hunk = result.hunks[0] + expect(hunk.type).toBe("update") + expect(hunk.path).toBe(uncPath) + }) + + test("should parse Windows long paths", () => { + const longPath = String.raw`\\?\C:\long\path\file.txt` + const patchText = `*** Begin Patch +*** Update File: ${longPath} +@@ +-old ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + const hunk = result.hunks[0] + expect(hunk.type).toBe("update") + expect(hunk.path).toBe(longPath) + }) + + test("should parse Windows ADS paths", () => { + const adsPath = String.raw`C:\path\file.txt:stream` + const patchText = `*** Begin Patch +*** Update File: ${adsPath} +@@ +-old ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + const hunk = result.hunks[0] + expect(hunk.type).toBe("update") + expect(hunk.path).toBe(adsPath) + }) + + test("should parse Windows drive-letter add paths", () => { + const addPath = String.raw`D:\Root\new.txt` + const patchText = `*** Begin Patch +*** Add File: ${addPath} ++new +*** End Patch` + + const result = Patch.parsePatch(patchText) + const hunk = result.hunks[0] + expect(hunk.type).toBe("add") + expect(hunk.path).toBe(addPath) + }) + + test("should parse Windows drive-letter delete paths", () => { + const deletePath = String.raw`D:\Root\old.txt` + const patchText = `*** Begin Patch +*** Delete File: ${deletePath} +*** End Patch` + + const result = Patch.parsePatch(patchText) + const hunk = result.hunks[0] + expect(hunk.type).toBe("delete") + expect(hunk.path).toBe(deletePath) + }) + test("should throw error for invalid patch format", () => { const invalidPatch = `This is not a valid patch`