Documentation
¶
Overview ¶
Package worktree makes "one sandbox per git branch" a one-liner. It manages git worktrees in a sandbox-owned location (under the config dir, so the user's project directory stays clean) so several agents can run in parallel, each on its own branch in its own container, without colliding — then reviewed with a normal `git checkout <branch>`.
Index ¶
- func Ahead(dir, branch, base string) int
- func Behind(dir, branch, base string) int
- func Branch(dir string) string
- func CommitAll(dir, branch, message string) (committed bool, err error)
- func CommitFileDiff(dir, sha, path string) string
- func Dirty(dir, branch string, limit int) []string
- func FileDiff(dir string, args ...string) string
- func Git(dir, branch string, args ...string) error
- func GitCommonDir(dir string) (path string, ok bool)
- func Head(dir, branch string) string
- func HeadBranch(dir string) string
- func InCommit(dir, commit, path string) bool
- func IsClean(dir string) (bool, error)
- func IsGitDir(path string) bool
- func MainRepo(dir string) string
- func Merge(dir, branch string) error
- func Path(dir, branch string) (path string, exists bool, err error)
- func Remove(dir, branch string, force bool) error
- func RepoID(dir string) (string, error)
- func RepoRoot(dir string) (string, error)
- func SanitizeName(s string) string
- func UntrackedContent(dir, path string) string
- type Commit
- type ErrGitFailed
- type FileStat
- type Info
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Ahead ¶
Ahead counts the commits on branch that are not yet in base — the "did this agent produce anything I haven't got?" number. Returns 0 for an unknown branch or a non-repository rather than an error: it is a status column, and a missing count must never fail the listing.
func Behind ¶
Behind counts commits on base that branch does not have — how far a branch has fallen behind what it is meant to land on.
The mirror of Ahead, and worth having alongside it: "3 ahead" says there is something to land, "3 ahead, 40 behind" says landing it will be a merge. Same bargain as Ahead on failure — an unanswerable question counts as zero, because this is a status number and one broken worktree must not blank the rest.
func Branch ¶
Branch reports the branch checked out in the repository containing dir, or "" when dir is not a git repository (git isn't required to use the sandbox). A detached HEAD has no branch name, so the short commit id stands in.
func CommitAll ¶
CommitAll stages everything in branch's worktree (including untracked files) and commits it with message. It reports committed=false, with no error, when the worktree is clean — landing a branch whose agent already committed is not a failure. A non-zero git exit is returned as *ErrGitFailed.
It verifies that the checkout is still *on* branch before staging anything, and that check is the point rather than a formality. Path falls back to the name-derived directory and reports it as existing, so an agent that ran `git checkout -b` inside its own worktree — the exact drift this package exists to handle — would otherwise have `add -A && commit` land its work on whatever branch the checkout moved to, and the merge that follows would take the untouched original. A commit made on the wrong branch after a refusal would be the worst of both.
The dirty check is a direct `status --porcelain` rather than Dirty(), which swallows every error by design because it feeds a status column. Here an unreadable worktree must refuse: reading it as "clean" would merge the branch without the agent's uncommitted work and report success.
func CommitFileDiff ¶
CommitFileDiff returns one file's unified diff within one commit.
func Dirty ¶
Dirty reports the paths of modified or untracked files in the worktree for branch, capped at limit entries (0 = uncapped). Used to warn at exit that work lives only in the worktree, rather than letting it surface much later as a confusing `worktree rm` refusal. Any error yields no paths: this is a best-effort nicety and must never fail a run.
func FileDiff ¶
FileDiff returns the unified diff of one path, as git writes it.
rev selects what to compare against: a range for committed work, "HEAD" for what is uncommitted in a checkout. Parsing is left to the caller — this package owns talking to git, and every call here goes through runGit so the repository's own config cannot make git run commands (internal/githard).
func Git ¶
Git runs git inside the worktree for branch, streaming output to the caller's stdout/stderr, so the worktree can be operated on by branch name instead of requiring the user to cd into the sandbox-owned directory. A non-zero git exit is returned as *ErrGitFailed.
func GitCommonDir ¶
GitCommonDir reports the repository's main .git directory when dir is a git worktree — i.e. when its ".git" is a pointer *file* rather than a directory.
This matters for the sandbox: a worktree's .git file holds an absolute host path into the parent repo (".git/worktrees/<name>"), and that path is outside the workspace, so inside the container git would resolve the pointer, find nothing, and fail with "not a git repository". Mounting the returned directory at the same absolute path makes git work normally in the sandbox.
ok is false for a normal repository (.git is a directory) or a non-repository, where nothing extra needs mounting.
func Head ¶
Head returns the abbreviated commit id a branch points at, or "" when it cannot be read. Abbreviated because it is shown, not resolved: a caller that needs to address the commit has the branch name, which is what every other operation here takes.
func HeadBranch ¶
HeadBranch reports the branch checked out in the repository containing dir, or "" when HEAD is detached (or dir is not a repository). Unlike Branch it does *not* fall back to a commit id: a caller about to merge into "the current branch" needs a real branch name, and a detached HEAD is a reason to refuse, not something to paper over with a sha.
func IsClean ¶
IsClean reports whether the working tree at dir has no staged, unstaged, or untracked changes. A landing refuses to merge into a dirty checkout, where a merge would entangle the user's in-progress work with the merge commit.
func IsGitDir ¶
IsGitDir reports whether path holds the markers of a git directory — HEAD and objects/ — rather than merely existing. Exported for internal/rescue, whose repair path faces the same question about the same untrusted input: a `.git` pointer file inside the workspace naming a directory sandbox-cli is about to write to.
func MainRepo ¶
MainRepo reports the absolute path of the *main* repository containing dir, following a linked worktree back to the repo it belongs to. It returns "" when dir is not in a git repository (git is not required to use the sandbox).
This is what makes "every sandbox container for this project" answerable: with --worktree each agent runs in a different directory, so only the shared parent repository identifies them as belonging together.
func Merge ¶
Merge merges branch into the branch currently checked out in the repository containing dir, always with --no-ff so the branch's history stays a visible, revertible unit. On a merge conflict git leaves the merge in progress and this returns *ErrGitFailed; the caller is expected to surface git's own message and stop rather than attempting any resolution.
func Path ¶
Path returns the managed worktree path for branch in the repo containing dir, and whether that worktree currently exists. It is the scriptable form of List — `cd "$(sandbox-cli worktree path BRANCH)"` — so nobody has to type the sandbox-owned directory by hand.
func Remove ¶
Remove deletes the sandbox worktree for branch (git worktree remove). When force is false git refuses if the worktree holds modified or untracked files, which is the safe default: those edits exist nowhere else.
func RepoID ¶
RepoID is the stable identity of the repository containing dir. Container names, container labels and managed worktree paths are all built from it, so they agree with each other by construction rather than by coincidence — which is what lets a later command find every container belonging to one repo.
func SanitizeName ¶
SanitizeName reduces a branch or repository identifier to one safe path or container-name segment ("feature/login" -> "feature-login"). Exported because container names are assembled from the same pieces as worktree paths: two spellings of one branch would mean two containers where the rule is one.
func UntrackedContent ¶
UntrackedContent returns a new file's contents, for showing it as an addition.
Untracked files are in no diff at all, and for an agent that scaffolds something they are the whole of the work — so "git has nothing to compare this against" must not become "there is nothing to show".
Types ¶
type Commit ¶
type Commit struct {
SHA string
ShortSHA string
Subject string
Author string
Date string
Files int
Insertions int
Deletions int
}
Commit is one commit on a branch, with what it touched.
type ErrGitFailed ¶
type ErrGitFailed struct{ Err error }
ErrGitFailed wraps a non-zero exit from the git subprocess. git has already written its own diagnostics to stderr, so callers should set an exit code rather than print the error again. Errors that are *not* this (an unknown branch, a missing repo) still need reporting.
func (*ErrGitFailed) Error ¶
func (e *ErrGitFailed) Error() string
func (*ErrGitFailed) Unwrap ¶
func (e *ErrGitFailed) Unwrap() error
type FileStat ¶
type FileStat struct {
Path string
Status string // "added" | "modified" | "deleted" | "renamed"
Insertions int
Deletions int
Binary bool
}
FileStat is one file's change in a diff: how it changed and by how much.
func CommitStat ¶
CommitStat reports what one commit changed, per file.
Through `show` rather than a diff between the commit and its parent, because the parent is not always there: a root commit has none, and `sha^` fails rather than returning the whole tree.
func StatBetween ¶
StatSince reports what changed in a checkout between two trees.
Both sides are built the same way — a snapshot written with `add -A`, so both hold untracked files — which is what makes the comparison honest. Diffing a snapshot against the working tree instead reports every untracked file as a deletion, because `git diff <commit>` only considers what git tracks while the snapshot holds everything.
This is the difference between "what is uncommitted here" and "what did that run change": anything the workspace already had is in the before-tree, so it cancels out rather than being credited to an agent that never touched it.
func WorkingStatIn ¶
WorkingStatIn reports what is changed but not committed in a checkout, addressed by directory rather than by branch.
By directory because a run's workspace is not always a managed worktree: a plain `sandbox-cli claude` mounts the repository you are standing in, and resolving "the worktree for this branch" then finds nothing and reports that the agent changed nothing at all — which is exactly wrong for the runs people look at most.
Included wherever a run's work is shown, because uncommitted is usually where an agent's output still is: one that wrote files and did not commit has produced the work worth reviewing, and a diff of commits alone calls that "nothing changed".