Documentation
¶
Overview ¶
Package gitworktree performs the repository WRITES Quil needs to give a pane its own linked worktree, plus the listing that offers the existing ones.
It is deliberately a separate package from gitinfo rather than a few more functions in it. gitinfo's package documentation states that every call is a read and nothing there can modify a repository — and that is load-bearing rather than descriptive: the daemon runs gitinfo.Probe on a ticker against every pane's checkout, so a package that gains the ability to write is one careless refactor away from a ticker that writes. Keeping the two apart makes that mistake need an import to happen, which is a thing a reviewer can see.
Pure and stdlib-only, a sibling of gitinfo, gitdiscover and kubediscover. It shells out to git plumbing rather than manipulating .git by hand — creating a worktree means writing a gitdir file, an admin directory under worktrees/<name>, and a checked-out tree, and getting any of it subtly wrong produces a repository git itself cannot repair.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Add ¶
Add creates a linked worktree at path, checking out a NEW branch off the repository's current HEAD. repo is the directory the command runs in.
There is deliberately no force option and no --force in the argv. Every refusal git can raise here is a fact the user needs — the path is occupied, the branch already exists, the branch is checked out in another worktree, the repository has no commits to branch from — and forcing past any of them lands a pane on top of a checkout something else is using. The error is returned with git's own stderr attached, because "already used by worktree '/x/feat-y'" tells the user which pane to go look at and no message this package could invent would.
func DerivePath ¶ added in v1.52.0
DerivePath returns where a worktree for branch belongs: a SIBLING of the repository, at <parent>/<repo><worktreesSuffix>/<flattened-branch>.
Sibling rather than nested so the repo tree stays clean — no .gitignore entry, and no tool that walks the tree (ripgrep, file watchers, the agent itself) finds a second full checkout inside the first.
Separators are flattened rather than preserved: feat/x is an ordinary branch name, and honouring it as a path would nest directories under the worktrees parent, so two branches could collide on an intermediate segment (feat/x and feat would want the same name to be both a file and a directory).
Callers must run ValidateBranch first — this function assumes the name cannot escape, and does not re-check.
func Remove ¶ added in v1.52.2
Remove undoes an Add whose pane could not be created, and deletes the branch that Add made along with it.
This is the ONE place --force is right, and for a reason that does not generalise to Add: the worktree being removed was created by this daemon seconds ago and has never been handed to anyone, so there is no user work to protect — while without it git refuses to remove a worktree it considers dirty, which a fresh checkout can be on a repository with line-ending or filemode differences. Leaving it instead strands a full checkout on disk plus a branch pointing at it, and the next attempt at the same name then fails with "already exists" against a directory the user never made.
Best-effort by contract: the caller is already reporting a failure and this is cleanup, so the error is for the log, not for the user.
func ValidateBranch ¶ added in v1.52.0
ValidateBranch rejects a name that cannot safely be BOTH a git ref and a path segment.
Two grammars, deliberately, and neither subsumes the other. The name reaches `git worktree add -b <branch>` as argv — so a leading dash reads as a flag, and git's own ref rules bar the rest — and it also becomes a path segment via DerivePath, so it must not be able to reach outside its parent directory. A name legal as one can be dangerous as the other.
Slashes are ACCEPTED: feat/x is an ordinary branch name. DerivePath flattens them rather than this function rejecting them.
git itself would refuse most of these, and that is not a reason to skip the check: the refusal would arrive as a failed subprocess seconds later, after a permit and a single-flight slot have been spent, rather than as a message beside the field the user typed into.
Types ¶
type Worktree ¶
type Worktree struct {
// Path is the working directory, absolute, as git reports it.
Path string
// Branch is the checked-out branch with the refs/heads/ prefix stripped.
// Empty when Detached or Bare.
Branch string
Detached bool
// Main marks the repository's primary checkout — the first block git
// prints. It is where a NEW worktree's path is derived from, and it is the
// one entry that must never be offered as somewhere to attach.
Main bool
// Locked and Prunable mark entries git will refuse to operate on.
Locked bool
Prunable bool
// Bare marks a repository with no working tree. Only ever true on the main
// entry, and it cannot host a pane.
Bare bool
}
Worktree is one entry of `git worktree list --porcelain`.
Locked and Prunable are carried rather than filtered because both describe an entry that LOOKS attachable and is not: a prunable worktree's directory is gone, and a locked one refuses operations. Dropping them would hand the caller a shorter list with no way to explain the absence, so a pane could be offered a directory that cannot host it.
func List ¶
List reports the worktrees of the repository containing dir, main checkout first. The caller supplies the timeout via ctx.
A directory outside any repository is NOT an error: the setup dialog asks about every directory the user browses to, and most are not repositories, so treating that as a failure would put an error on screen for the ordinary case. It returns no entries, which is the same shape the caller renders for a repository with nothing to attach to.
That collapse is deliberately narrow: a plain non-zero exit from `git worktree list` — the shape "not a repository" actually takes — is the only case folded into (nil, nil). A missing git binary (exec.ErrNotFound) and a call that ran out of time (ctx.Err() != nil) are returned as errors instead, or a missing binary, a corrupt repository and a permissions error would all render the same confidently-wrong "not a git repository" the caller shows for the genuine case.