summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax <[email protected]>2026-03-25 00:43:48 -0400
committerGitHub <[email protected]>2026-03-25 04:43:48 +0000
commit0a80ef4278c252cb8dca72cae5d5c5748cec7e9a (patch)
tree0691a9be519f4e2099af33d82eaafd88617ae612
parent4f9667c4bb2fed7fdd87e7eceab3acfd248ccf9f (diff)
downloadopencode-0a80ef4278c252cb8dca72cae5d5c5748cec7e9a.tar.gz
opencode-0a80ef4278c252cb8dca72cae5d5c5748cec7e9a.zip
fix(opencode): avoid snapshotting files over 2MB (#19043)
-rw-r--r--packages/opencode/src/snapshot/index.ts68
-rw-r--r--packages/opencode/test/snapshot/snapshot.test.ts19
2 files changed, 77 insertions, 10 deletions
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 7068545d2..ec07173c8 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -34,6 +34,7 @@ export namespace Snapshot {
const log = Log.create({ service: "snapshot" })
const prune = "7.days"
+ const limit = 2 * 1024 * 1024
const core = ["-c", "core.longpaths=true", "-c", "core.symlinks=true"]
const cfg = ["-c", "core.autocrlf=false", ...core]
const quote = [...cfg, "-c", "core.quotepath=false"]
@@ -123,20 +124,69 @@ export namespace Snapshot {
return file
})
- const sync = Effect.fnUntraced(function* () {
+ const sync = Effect.fnUntraced(function* (list: string[] = []) {
const file = yield* excludes()
const target = path.join(state.gitdir, "info", "exclude")
+ const text = [
+ file ? (yield* read(file)).trimEnd() : "",
+ ...list.map((item) => `/${item.replaceAll("\\", "/")}`),
+ ]
+ .filter(Boolean)
+ .join("\n")
yield* fs.ensureDir(path.join(state.gitdir, "info")).pipe(Effect.orDie)
- if (!file) {
- yield* fs.writeFileString(target, "").pipe(Effect.orDie)
- return
- }
- yield* fs.writeFileString(target, yield* read(file)).pipe(Effect.orDie)
+ yield* fs.writeFileString(target, text ? `${text}\n` : "").pipe(Effect.orDie)
})
const add = Effect.fnUntraced(function* () {
yield* sync()
- yield* git([...cfg, ...args(["add", "."])], { cwd: state.directory })
+ const [diff, other] = yield* Effect.all(
+ [
+ git([...quote, ...args(["diff-files", "--name-only", "-z", "--", "."])], {
+ cwd: state.directory,
+ }),
+ git([...quote, ...args(["ls-files", "--others", "--exclude-standard", "-z", "--", "."])], {
+ cwd: state.directory,
+ }),
+ ],
+ { concurrency: 2 },
+ )
+ if (diff.code !== 0 || other.code !== 0) {
+ log.warn("failed to list snapshot files", {
+ diffCode: diff.code,
+ diffStderr: diff.stderr,
+ otherCode: other.code,
+ otherStderr: other.stderr,
+ })
+ return
+ }
+
+ const tracked = diff.text.split("\0").filter(Boolean)
+ const all = Array.from(new Set([...tracked, ...other.text.split("\0").filter(Boolean)]))
+ if (!all.length) return
+
+ const large = (yield* Effect.all(
+ all.map((item) =>
+ fs
+ .stat(path.join(state.directory, item))
+ .pipe(Effect.catch(() => Effect.void))
+ .pipe(
+ Effect.map((stat) => {
+ if (!stat || stat.type !== "File") return
+ const size = typeof stat.size === "bigint" ? Number(stat.size) : stat.size
+ return size > limit ? item : undefined
+ }),
+ ),
+ ),
+ { concurrency: 8 },
+ )).filter((item): item is string => Boolean(item))
+ yield* sync(large)
+ const result = yield* git([...cfg, ...args(["add", "--sparse", "."])], { cwd: state.directory })
+ if (result.code !== 0) {
+ log.warn("failed to add snapshot files", {
+ exitCode: result.code,
+ stderr: result.stderr,
+ })
+ }
})
const cleanup = Effect.fnUntraced(function* () {
@@ -177,7 +227,7 @@ export namespace Snapshot {
const patch = Effect.fnUntraced(function* (hash: string) {
yield* add()
const result = yield* git(
- [...quote, ...args(["diff", "--no-ext-diff", "--name-only", hash, "--", "."])],
+ [...quote, ...args(["diff", "--cached", "--no-ext-diff", "--name-only", hash, "--", "."])],
{
cwd: state.directory,
},
@@ -245,7 +295,7 @@ export namespace Snapshot {
const diff = Effect.fnUntraced(function* (hash: string) {
yield* add()
- const result = yield* git([...quote, ...args(["diff", "--no-ext-diff", hash, "--", "."])], {
+ const result = yield* git([...quote, ...args(["diff", "--cached", "--no-ext-diff", hash, "--", "."])], {
cwd: state.worktree,
})
if (result.code !== 0) {
diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts
index bf54feb47..f42cec4fc 100644
--- a/packages/opencode/test/snapshot/snapshot.test.ts
+++ b/packages/opencode/test/snapshot/snapshot.test.ts
@@ -181,7 +181,7 @@ test("symlink handling", async () => {
})
})
-test("large file handling", async () => {
+test("file under size limit handling", async () => {
await using tmp = await bootstrap()
await Instance.provide({
directory: tmp.path,
@@ -196,6 +196,23 @@ test("large file handling", async () => {
})
})
+test("large added files are skipped", async () => {
+ await using tmp = await bootstrap()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const before = await Snapshot.track()
+ expect(before).toBeTruthy()
+
+ await Filesystem.write(`${tmp.path}/huge.txt`, new Uint8Array(2 * 1024 * 1024 + 1))
+
+ expect((await Snapshot.patch(before!)).files).toEqual([])
+ expect(await Snapshot.diff(before!)).toBe("")
+ expect(await Snapshot.track()).toBe(before)
+ },
+ })
+})
+
test("nested directory revert", async () => {
await using tmp = await bootstrap()
await Instance.provide({