sandlot/src/git.ts
2026-02-17 08:52:07 -08:00

67 lines
2.4 KiB
TypeScript

import { existsSync } from "fs"
import { rm } from "fs/promises"
import { $ } from "bun"
/** Get the repo root from a working directory. */
export async function repoRoot(cwd?: string): Promise<string> {
const result = await $`git rev-parse --show-toplevel`.cwd(cwd ?? ".").text()
return result.trim()
}
/** Get the current branch name. */
export async function currentBranch(cwd?: string): Promise<string> {
const result = await $`git rev-parse --abbrev-ref HEAD`.cwd(cwd ?? ".").text()
return result.trim()
}
/** Check if a branch exists locally or remotely. Returns "local", "remote", or null. */
export async function branchExists(branch: string, cwd?: string): Promise<"local" | "remote" | null> {
const dir = cwd ?? "."
const local = await $`git show-ref --verify --quiet refs/heads/${branch}`.cwd(dir).nothrow().quiet()
if (local.exitCode === 0) return "local"
await $`git fetch origin`.cwd(dir).nothrow().quiet()
const remote = await $`git show-ref --verify --quiet refs/remotes/origin/${branch}`.cwd(dir).nothrow().quiet()
if (remote.exitCode === 0) return "remote"
return null
}
/** Create a worktree for the given branch. */
export async function createWorktree(branch: string, worktreePath: string, cwd: string): Promise<void> {
// Clean up stale worktree path if it exists
if (existsSync(worktreePath)) {
await $`git worktree remove ${worktreePath} --force`.cwd(cwd).nothrow().quiet()
if (existsSync(worktreePath)) {
await rm(worktreePath, { recursive: true })
}
}
await $`git worktree prune`.cwd(cwd).nothrow().quiet()
const exists = await branchExists(branch, cwd)
if (exists === "local") {
await $`git worktree add ${worktreePath} ${branch}`.cwd(cwd).quiet()
} else if (exists === "remote") {
await $`git worktree add ${worktreePath} -b ${branch} origin/${branch}`.cwd(cwd).quiet()
} else {
// New branch from current HEAD
await $`git worktree add -b ${branch} ${worktreePath}`.cwd(cwd).quiet()
}
}
/** Remove a worktree. */
export async function removeWorktree(worktreePath: string, cwd: string): Promise<void> {
await $`git worktree remove ${worktreePath} --force`.cwd(cwd)
}
/** Delete a local branch. */
export async function deleteLocalBranch(branch: string, cwd: string): Promise<void> {
await $`git branch -D ${branch}`.cwd(cwd).nothrow()
}
/** Checkout a branch. */
export async function checkout(branch: string, cwd: string): Promise<void> {
await $`git checkout ${branch}`.cwd(cwd)
}