Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions packages/opencode/src/patch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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++
}

Expand Down
31 changes: 31 additions & 0 deletions packages/opencode/test/patch/patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
Loading