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
22 changes: 10 additions & 12 deletions packages/opencode/src/patch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
107 changes: 107 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,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`

Expand Down