summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/app/src/context/file.tsx18
-rw-r--r--packages/opencode/src/snapshot/index.ts90
2 files changed, 94 insertions, 14 deletions
diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx
index 5ea499387..d7630509a 100644
--- a/packages/app/src/context/file.tsx
+++ b/packages/app/src/context/file.tsx
@@ -195,7 +195,20 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
const root = directory()
const prefix = root.endsWith("/") ? root : root + "/"
- let path = stripQueryAndHash(stripFileProtocol(input))
+ let path = input
+
+ // Only strip protocol and decode if it's a file URI
+ if (path.startsWith("file://")) {
+ const raw = stripQueryAndHash(stripFileProtocol(path))
+ try {
+ // Attempt to treat as a standard URI
+ path = decodeURIComponent(raw)
+ } catch {
+ // Fallback for legacy paths that might contain invalid URI sequences (e.g. "100%")
+ // In this case, we treat the path as raw, but still strip the protocol
+ path = raw
+ }
+ }
if (path.startsWith(prefix)) {
path = path.slice(prefix.length)
@@ -218,7 +231,8 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
function tab(input: string) {
const path = normalize(input)
- return `file://${path}`
+ const encoded = path.split("/").map(encodeURIComponent).join("/")
+ return `file://${encoded}`
}
function pathFromTab(tabValue: string) {
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 46c97cf8d..5a198434b 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -104,6 +104,7 @@ export namespace Snapshot {
.split("\n")
.map((x) => x.trim())
.filter(Boolean)
+ .map((x) => unquote(x))
.map((x) => path.join(Instance.worktree, x)),
}
}
@@ -151,7 +152,7 @@ export namespace Snapshot {
})
} else {
log.info("file did not exist in snapshot, deleting", { file })
- await fs.unlink(file).catch(() => {})
+ await fs.unlink(file).catch(() => { })
}
}
files.add(file)
@@ -202,20 +203,22 @@ export namespace Snapshot {
.nothrow()
.lines()) {
if (!line) continue
- const [additions, deletions, file] = line.split("\t")
+ const [additions, deletions, rawFile] = line.split("\t")
+ const file = unquote(rawFile)
const isBinaryFile = additions === "-" && deletions === "-"
- const before = isBinaryFile
- ? ""
+ const beforeResult = isBinaryFile
+ ? { exitCode: 0, text: () => "", stderr: Buffer.from("") }
: await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} show ${from}:${file}`
- .quiet()
- .nothrow()
- .text()
- const after = isBinaryFile
- ? ""
+ .quiet()
+ .nothrow()
+ const before = beforeResult.exitCode === 0 ? beforeResult.text() : `[DEBUG ERROR] git show ${from}:${file} failed: ${beforeResult.stderr.toString()}`
+
+ const afterResult = isBinaryFile
+ ? { exitCode: 0, text: () => "", stderr: Buffer.from("") }
: await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} show ${to}:${file}`
- .quiet()
- .nothrow()
- .text()
+ .quiet()
+ .nothrow()
+ const after = afterResult.exitCode === 0 ? afterResult.text() : `[DEBUG ERROR] git show ${to}:${file} failed: ${afterResult.stderr.toString()}`
const added = isBinaryFile ? 0 : parseInt(additions)
const deleted = isBinaryFile ? 0 : parseInt(deletions)
result.push({
@@ -229,6 +232,69 @@ export namespace Snapshot {
return result
}
+ export function unquote(path: string): string {
+ // If the path is wrapped in quotes, it might contain octal escapes
+ if (path.startsWith('"') && path.endsWith('"')) {
+ const quoted = path.slice(1, -1)
+ // Decode escaped characters
+ const buffer: number[] = []
+ for (let i = 0; i < quoted.length; i++) {
+ if (quoted[i] === "\\") {
+ i++
+ // Check for octal escape (e.g. \344)
+ if (i + 2 < quoted.length && /^[0-7]{3}$/.test(quoted.slice(i, i + 3))) {
+ const octal = quoted.slice(i, i + 3)
+ buffer.push(parseInt(octal, 8))
+ i += 2
+ } else {
+ // Handle standard escapes
+ switch (quoted[i]) {
+ case "b":
+ buffer.push(8)
+ break
+ case "t":
+ buffer.push(9)
+ break
+ case "n":
+ buffer.push(10)
+ break
+ case "v":
+ buffer.push(11)
+ break
+ case "f":
+ buffer.push(12)
+ break
+ case "r":
+ buffer.push(13)
+ break
+ case '"':
+ buffer.push(34)
+ break
+ case "\\":
+ buffer.push(92)
+ break
+ default:
+ // If unknown escape, keep original (or char code of escaped char)
+ buffer.push(quoted.charCodeAt(i))
+ }
+ }
+ } else {
+ const charCode = quoted.charCodeAt(i)
+ if (charCode < 128) {
+ buffer.push(charCode)
+ } else {
+ const charBuffer = Buffer.from(quoted[i])
+ for (const byte of charBuffer) {
+ buffer.push(byte)
+ }
+ }
+ }
+ }
+ return Buffer.from(buffer).toString("utf8")
+ }
+ return path
+ }
+
function gitdir() {
const project = Instance.project
return path.join(Global.Path.data, "snapshot", project.id)