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
99 changes: 80 additions & 19 deletions packages/opencode/src/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import z from "zod"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
import { Scheduler } from "../scheduler"
import { Filesystem } from "../util/filesystem"

export namespace Snapshot {
const log = Log.create({ service: "snapshot" })
Expand Down Expand Up @@ -61,12 +62,12 @@ export namespace Snapshot {
})
.quiet()
.nothrow()
// Configure git to not convert line endings on Windows
await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
await $`git --git-dir ${git} config core.quotepath false`.quiet().nothrow()
log.info("initialized")
}
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
await $`git -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const hash = await $`git -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
.quiet()
.cwd(Instance.directory)
.nothrow()
Expand All @@ -85,7 +86,7 @@ export namespace Snapshot {
const git = gitdir()
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
const result =
await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .`
await $`git -c core.autocrlf=false -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .`
.quiet()
.cwd(Instance.directory)
.nothrow()
Expand Down Expand Up @@ -133,22 +134,41 @@ export namespace Snapshot {
for (const item of patches) {
for (const file of item.files) {
if (files.has(file)) continue
log.info("reverting", { file, hash: item.hash })
const result = await $`git --git-dir ${git} --work-tree ${Instance.worktree} checkout ${item.hash} -- ${file}`
.quiet()
.cwd(Instance.worktree)
.nothrow()
const relativePath = path.relative(Instance.worktree, file)
if (!Filesystem.contains(Instance.worktree, file) || path.isAbsolute(relativePath) || relativePath === "") {
log.warn("skipping file outside worktree", { file, relativePath, worktree: Instance.worktree })
files.add(file)
continue
}
const gitPath = process.platform === "win32" ? relativePath.replaceAll("\\", "/") : relativePath
log.info("reverting", { file, gitPath, hash: item.hash })
const result = await runGitCommand(
["checkout", item.hash, "--", gitPath],
{ gitDir: git, workTree: Instance.worktree, cwd: Instance.worktree },
)
if (result.exitCode !== 0) {
const relativePath = path.relative(Instance.worktree, file)
const checkTree =
await $`git --git-dir ${git} --work-tree ${Instance.worktree} ls-tree ${item.hash} -- ${relativePath}`
.quiet()
.cwd(Instance.worktree)
.nothrow()
if (checkTree.exitCode === 0 && checkTree.text().trim()) {
log.info("file existed in snapshot but checkout failed, keeping", {
file,
})
const checkTree = await runGitCommand(
["ls-tree", item.hash, "--", gitPath],
{ gitDir: git, workTree: Instance.worktree, cwd: Instance.worktree },
)
if (checkTree.exitCode === 0 && checkTree.stdout.trim()) {
log.info("checkout failed, trying git show fallback", { file, gitPath })
const content = await runGitCommand(
["show", `${item.hash}:${gitPath}`],
{ gitDir: git, cwd: Instance.worktree },
)
if (content.exitCode === 0) {
const dir = path.dirname(file)
await fs.mkdir(dir, { recursive: true })
await fs.writeFile(file, content.stdoutBuffer)
log.info("restored file via git show", { file })
} else {
log.warn("failed to restore file, keeping current version", {
file,
checkoutError: result.stderr,
showError: content.stderr,
})
}
} else {
log.info("file did not exist in snapshot, deleting", { file })
await fs.unlink(file).catch(() => {})
Expand All @@ -159,6 +179,47 @@ export namespace Snapshot {
}
}

interface GitCommandOptions {
gitDir?: string
workTree?: string
cwd?: string
}

interface GitCommandResult {
exitCode: number
stdout: string
stderr: string
stdoutBuffer: Buffer
}

async function runGitCommand(args: string[], options: GitCommandOptions): Promise<GitCommandResult> {
const gitArgs = ["-c", "core.quotepath=false"]
if (options.gitDir) {
gitArgs.push("--git-dir", options.gitDir)
}
if (options.workTree) {
gitArgs.push("--work-tree", options.workTree)
}
gitArgs.push(...args)

const proc = Bun.spawn(["git", ...gitArgs], {
cwd: options.cwd,
stdout: "pipe",
stderr: "pipe",
})

const exitCode = await proc.exited
const stdoutBuffer = Buffer.from(await new Response(proc.stdout).arrayBuffer())
const stderr = await new Response(proc.stderr).text()

return {
exitCode,
stdout: stdoutBuffer.toString("utf-8"),
stderr,
stdoutBuffer,
}
}

export async function diff(hash: string) {
const git = gitdir()
await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow()
Expand Down
Loading