worktree

package
v0.0.0-...-a271580 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 13 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Ahead

func Ahead(dir, branch, base string) int

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

func Behind(dir, branch, base string) int

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

func Branch(dir string) string

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

func CommitAll(dir, branch, message string) (committed bool, err error)

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

func CommitFileDiff(dir, sha, path string) string

CommitFileDiff returns one file's unified diff within one commit.

func Dirty

func Dirty(dir, branch string, limit int) []string

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

func FileDiff(dir string, args ...string) string

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

func Git(dir, branch string, args ...string) error

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

func GitCommonDir(dir string) (path string, ok bool)

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(dir, branch string) string

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

func HeadBranch(dir string) string

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 InCommit

func InCommit(dir, commit, path string) bool

InCommit reports whether a path existed in a commit's tree.

func IsClean

func IsClean(dir string) (bool, error)

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

func IsGitDir(path string) bool

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

func MainRepo(dir string) string

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

func Merge(dir, branch string) error

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

func Path(dir, branch string) (path string, exists bool, err error)

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

func Remove(dir, branch string, force bool) error

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

func RepoID(dir string) (string, error)

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 RepoRoot

func RepoRoot(dir string) (string, error)

RepoRoot returns the top-level directory of the git repository containing dir.

func SanitizeName

func SanitizeName(s string) string

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

func UntrackedContent(dir, path string) string

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.

func Commits

func Commits(dir, branch, base string, limit int) []Commit

Commits lists what a branch has that its base does not, newest first.

Three dots would be wrong here: this is the branch's own history, so the two-dot range is the question — "what is on this branch and not on base", without replaying what happened on base meanwhile.

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

func CommitStat(dir, sha string) []FileStat

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 DiffStat

func DiffStat(dir, branch, base string) []FileStat

DiffStat reports what branch committed beyond base, per file.

func StatBetween

func StatBetween(dir, before, after string) []FileStat

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

func WorkingStatIn(dir string) []FileStat

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".

type Info

type Info struct {
	Branch  string
	Path    string
	Created bool // true when Resolve created it this call (vs. reused)
}

Info describes a resolved worktree.

func List

func List(dir string) ([]Info, error)

List returns the sandbox-managed worktrees for the repo containing dir (those living under the managed base directory).

func Resolve

func Resolve(dir, branch string) (Info, error)

Resolve ensures a git worktree for branch exists in the repo containing dir and returns it. If the branch does not exist it is created from the current HEAD. An existing sandbox worktree for the branch is reused.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL