diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index d049ec4373c..a9e4b232966 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -911,6 +911,7 @@ export function Session() { viewportOptions={{ paddingRight: showScrollbar() ? 1 : 0, }} + viewportCulling={true} verticalScrollbarOptions={{ paddingLeft: 1, visible: showScrollbar(), diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 0bbb1115e61..f0ba009b27c 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -15,26 +15,29 @@ export namespace Snapshot { const cfg = await Config.get() if (cfg.snapshot === false) return const git = gitdir() + const worktree = await worktreeRoot() + const env = { ...process.env, GIT_INDEX_FILE: indexFile(git, worktree) } if (await fs.mkdir(git, { recursive: true })) { await $`git init` .env({ - ...process.env, + ...env, GIT_DIR: git, - GIT_WORK_TREE: Instance.worktree, + GIT_WORK_TREE: worktree, }) .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.autocrlf false`.env(env).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 --git-dir ${git} --work-tree ${worktree} add .`.env(env).quiet().cwd(Instance.directory).nothrow() + const hash = await $`git --git-dir ${git} --work-tree ${worktree} write-tree` + .env(env) .quiet() .cwd(Instance.directory) .nothrow() .text() - log.info("tracking", { hash, cwd: Instance.directory, git }) + log.info("tracking", { hash, cwd: Instance.directory, git, worktree }) return hash.trim() } @@ -46,9 +49,12 @@ export namespace Snapshot { export async function patch(hash: string): Promise { const git = gitdir() - await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow() + const worktree = await worktreeRoot() + const env = { ...process.env, GIT_INDEX_FILE: indexFile(git, worktree) } + await $`git --git-dir ${git} --work-tree ${worktree} add .`.env(env).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 --git-dir ${git} --work-tree ${worktree} diff --no-ext-diff --name-only ${hash} -- .` + .env(env) .quiet() .cwd(Instance.directory) .nothrow() @@ -67,17 +73,20 @@ export namespace Snapshot { .split("\n") .map((x) => x.trim()) .filter(Boolean) - .map((x) => path.join(Instance.worktree, x)), + .map((x) => path.join(worktree, x)), } } export async function restore(snapshot: string) { log.info("restore", { commit: snapshot }) const git = gitdir() + const worktree = await worktreeRoot() + const env = { ...process.env, GIT_INDEX_FILE: indexFile(git, worktree) } const result = - await $`git --git-dir ${git} --work-tree ${Instance.worktree} read-tree ${snapshot} && git --git-dir ${git} --work-tree ${Instance.worktree} checkout-index -a -f` + await $`git --git-dir ${git} --work-tree ${worktree} read-tree ${snapshot} && git --git-dir ${git} --work-tree ${worktree} checkout-index -a -f` + .env(env) .quiet() - .cwd(Instance.worktree) + .cwd(worktree) .nothrow() if (result.exitCode !== 0) { @@ -93,20 +102,25 @@ export namespace Snapshot { export async function revert(patches: Patch[]) { const files = new Set() const git = gitdir() + const worktree = await worktreeRoot() + const env = { ...process.env, GIT_INDEX_FILE: indexFile(git, worktree) } 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}` + const relativePath = path.relative(worktree, file) + const pathspec = relativePath.startsWith("..") ? file : relativePath + const result = await $`git --git-dir ${git} --work-tree ${worktree} checkout ${item.hash} -- ${pathspec}` + .env(env) .quiet() - .cwd(Instance.worktree) + .cwd(worktree) .nothrow() 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}` + await $`git --git-dir ${git} --work-tree ${worktree} ls-tree ${item.hash} -- ${relativePath}` + .env(env) .quiet() - .cwd(Instance.worktree) + .cwd(worktree) .nothrow() if (checkTree.exitCode === 0 && checkTree.text().trim()) { log.info("file existed in snapshot but checkout failed, keeping", { @@ -124,11 +138,14 @@ export namespace Snapshot { export async function diff(hash: string) { const git = gitdir() - await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`.quiet().cwd(Instance.directory).nothrow() + const worktree = await worktreeRoot() + const env = { ...process.env, GIT_INDEX_FILE: indexFile(git, worktree) } + await $`git --git-dir ${git} --work-tree ${worktree} add .`.env(env).quiet().cwd(Instance.directory).nothrow() const result = - await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .` + await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${worktree} diff --no-ext-diff ${hash} -- .` + .env(env) .quiet() - .cwd(Instance.worktree) + .cwd(Instance.directory) .nothrow() if (result.exitCode !== 0) { @@ -158,8 +175,11 @@ export namespace Snapshot { export type FileDiff = z.infer export async function diffFull(from: string, to: string): Promise { const git = gitdir() + const worktree = await worktreeRoot() + const env = { ...process.env, GIT_INDEX_FILE: indexFile(git, worktree) } const result: FileDiff[] = [] - for await (const line of $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .` + for await (const line of $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${worktree} diff --no-ext-diff --no-renames --numstat ${from} ${to} -- .` + .env(env) .quiet() .cwd(Instance.directory) .nothrow() @@ -169,13 +189,15 @@ export namespace Snapshot { const isBinaryFile = additions === "-" && deletions === "-" const before = isBinaryFile ? "" - : await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} show ${from}:${file}` + : await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${worktree} show ${from}:${file}` + .env(env) .quiet() .nothrow() .text() const after = isBinaryFile ? "" - : await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${Instance.worktree} show ${to}:${file}` + : await $`git -c core.autocrlf=false --git-dir ${git} --work-tree ${worktree} show ${to}:${file}` + .env(env) .quiet() .nothrow() .text() @@ -194,4 +216,17 @@ export namespace Snapshot { const project = Instance.project return path.join(Global.Path.data, "snapshot", project.id) } + + function indexFile(git: string, worktree: string) { + const key = Bun.hash.xxHash64(worktree).toString(16) + return path.join(git, `index-${key}`) + } + + async function worktreeRoot() { + const result = await $`git rev-parse --show-toplevel`.quiet().nothrow().cwd(Instance.directory) + if (result.exitCode !== 0) return Instance.worktree + const resolved = result.text().trim() + if (!resolved) return Instance.worktree + return path.resolve(Instance.directory, resolved) + } }