diff --git a/bun.lock b/bun.lock index 200e0587..a723e36b 100644 --- a/bun.lock +++ b/bun.lock @@ -31,6 +31,7 @@ "@openauthjs/openauth": "0.4.3", "@standard-schema/spec": "1.0.0", "ai": "catalog:", + "air": "0.4.14", "decimal.js": "10.5.0", "diff": "8.0.2", "env-paths": "3.0.0", @@ -516,6 +517,8 @@ "ai": ["ai@4.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="], + "air": ["air@0.4.14", "", { "dependencies": { "zephyr": "~1.3.5" } }, "sha512-E8bl9LlSGSQqjxxjeGIrpYpf8jVyJplsdK1bTobh61F7ks+3aLeXL4KbGSJIFsiaSSz5ZExLU51DGztmQSlZTQ=="], + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], @@ -1700,6 +1703,8 @@ "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + "zephyr": ["zephyr@1.3.6", "", {}, "sha512-oYH52DGZzIbXNrkijskaR8YpVKnXAe8jNgH1KirglVBnTFOn6mK9/0SVCxGn+73l0Hjhr4UYNzYkO07LXSWy6w=="], + "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], "zod-openapi": ["zod-openapi@4.2.4", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="], diff --git a/packages/opencode/.gitignore b/packages/opencode/.gitignore index 66857d89..e057ca61 100644 --- a/packages/opencode/.gitignore +++ b/packages/opencode/.gitignore @@ -1,4 +1,3 @@ -node_modules research dist gen diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index fcf77c45..95d7776c 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -1,14 +1,7 @@ import { App } from "../app/app" -import { - add, - commit, - init, - checkout, - statusMatrix, - remove, -} from "isomorphic-git" +import { $ } from "bun" import path from "path" -import fs from "fs" +import fs from "fs/promises" import { Ripgrep } from "../file/ripgrep" import { Log } from "../util/log" @@ -19,76 +12,52 @@ export namespace Snapshot { log.info("creating snapshot") const app = App.info() const git = gitdir(sessionID) - const files = await Ripgrep.files({ - cwd: app.path.cwd, - limit: app.git ? undefined : 1000, - }) - log.info("found files", { count: files.length }) - // not a git repo and too big to snapshot - if (!app.git && files.length === 1000) return - await init({ - dir: app.path.cwd, - gitdir: git, - fs, - }) - log.info("initialized") - const status = await statusMatrix({ - fs, - gitdir: git, - dir: app.path.cwd, - }) - log.info("matrix", { - count: status.length, - }) - const added = [] - for (const [file, head, workdir, stage] of status) { - if (workdir === 0 && stage === 1) { - log.info("remove", { file }) - await remove({ - fs, - gitdir: git, - dir: app.path.cwd, - filepath: file, - }) - continue - } - if (workdir !== head) { - added.push(file) - } + + // not a git repo, check if too big to snapshot + if (!app.git) { + const files = await Ripgrep.files({ + cwd: app.path.cwd, + limit: 1000, + }) + log.info("found files", { count: files.length }) + if (files.length > 1000) return } - log.info("removed files") - await add({ - fs, - gitdir: git, - parallel: true, - dir: app.path.cwd, - filepath: added, - }) + + if (await fs.mkdir(git, { recursive: true })) { + await $`git init` + .env({ + ...process.env, + GIT_DIR: git, + GIT_WORK_TREE: app.path.root, + }) + .quiet() + .nothrow() + log.info("initialized") + } + + await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow() log.info("added files") - const result = await commit({ - fs, - gitdir: git, - dir: app.path.cwd, - message: "snapshot", - author: { - name: "opencode", - email: "mail@opencode.ai", - }, - }) - log.info("commit", { result }) - return result + + const result = + await $`git --git-dir ${git} commit --allow-empty -m "snapshot" --author="opencode "` + .quiet() + .cwd(app.path.cwd) + .nothrow() + log.info("commit") + + // Extract commit hash from output like "[main abc1234] snapshot" + const match = result.stdout.toString().match(/\[.+ ([a-f0-9]+)\]/) + if (!match) throw new Error("Failed to extract commit hash") + return match[1] } export async function restore(sessionID: string, commit: string) { log.info("restore", { commit }) const app = App.info() - await checkout({ - fs, - gitdir: gitdir(sessionID), - dir: app.path.cwd, - ref: commit, - force: true, - }) + const git = gitdir(sessionID) + await $`git --git-dir=${git} checkout ${commit} --force` + .quiet() + .cwd(app.path.root) } function gitdir(sessionID: string) {