Skip to content
Closed
91 changes: 79 additions & 12 deletions packages/opencode/src/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,27 +300,94 @@ export namespace Snapshot {
const revert = Effect.fnUntraced(function* (patches: Snapshot.Patch[]) {
return yield* locked(
Effect.gen(function* () {
const groupByHash = new Map<string, string[]>()
const seen = new Set<string>()
for (const item of patches) {
const filesForHash = groupByHash.get(item.hash) ?? []
groupByHash.set(item.hash, filesForHash)
for (const file of item.files) {
if (seen.has(file)) continue
seen.add(file)
log.info("reverting", { file, hash: item.hash })
const result = yield* git([...core, ...args(["checkout", item.hash, "--", file])], {
cwd: state.worktree,
})
if (result.code !== 0) {
const rel = path.relative(state.worktree, file)
const tree = yield* git([...core, ...args(["ls-tree", item.hash, "--", rel])], {
filesForHash.push(file)
}
}

const revertSingle = Effect.fnUntraced(function* (hash: string, file: string) {

@natewill natewill Apr 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revertSingle is the same functionality as the revert function that is in the current code. We use this as a fallback if the batching doesn't work for some reason or the snapshot hash only has one reverted file in it

log.info("reverting", { file, hash })
const result = yield* git([...core, ...args(["checkout", hash, "--", file])], {
cwd: state.worktree,
})
if (result.code === 0) return
const rel = path.relative(state.worktree, file).replaceAll("\\", "/")
const tree = yield* git([...core, ...args(["ls-tree", hash, "--", rel])], {
cwd: state.worktree,
})
if (tree.code === 0 && tree.text.trim()) {
log.info("file existed in snapshot but checkout failed, keeping", { file, hash })
return
}
log.info("file did not exist in snapshot, deleting", { file, hash })
yield* remove(file)
})

for (const [hash, filesByHash] of groupByHash) {
for (let i = 0; i < filesByHash.length; i += 100) { //run git commands in 100 file chunks to prevent ARG_MAX errors
const fileChunk = filesByHash.slice(i, i + 100)
if (fileChunk.length === 1) {
yield* revertSingle(hash, fileChunk[0]!)
continue
}

const fileChunkPaths = fileChunk.map((file) => ({
rel: path.relative(state.worktree, file).replaceAll("\\", "/"),
file,
}))
const tree = yield* git(
[...core, ...args(["ls-tree", "--name-only", hash, "--", ...fileChunkPaths.map((item) => item.rel)])],
{
cwd: state.worktree,
},
)

if (tree.code !== 0) {
log.info("batched ls-tree failed, falling back to single-file revert", { hash, files: fileChunk.length })
for (const file of fileChunk) {
yield* revertSingle(hash, file)
}
continue
}

@natewill natewill Apr 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we only checkout files that exist in this snapshot; files missing from the tree should be removed. The old code did the same thing but individually.

const snapshotPaths = new Set(tree.text.trim().split("\n").map((item) => item.trim()).filter(Boolean))
const filesToCheckout: string[] = []
const missingFiles: string[] = []
for (const item of fileChunkPaths) {
if (snapshotPaths.has(item.rel)) {
filesToCheckout.push(item.file)
continue
}
missingFiles.push(item.file)
}

if (filesToCheckout.length) {
log.info("reverting", { hash, files: filesToCheckout.length })
const result = yield* git([...core, ...args(["checkout", hash, "--", ...filesToCheckout])], {
cwd: state.worktree,
})
if (tree.code === 0 && tree.text.trim()) {
log.info("file existed in snapshot but checkout failed, keeping", { file })
} else {
log.info("file did not exist in snapshot, deleting", { file })
yield* remove(file)
if (result.code !== 0) {
log.info("batched checkout failed, falling back to single-file revert", {
hash,
files: filesToCheckout.length,
})
for (const file of filesToCheckout) {
yield* revertSingle(hash, file)
}
}
}

for (const file of missingFiles) {
log.info("file did not exist in snapshot, deleting", { file, hash })
yield* remove(file)
}
}
}
}),
Expand Down
Loading