gitworktree

package
v1.52.2 Latest Latest
Warning

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

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

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

func Add(ctx context.Context, repo, path, branch string) error

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

func DerivePath(repoRoot, branch string) string

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

func Remove(ctx context.Context, repo, path string, branch string) error

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

func ValidateBranch(name string) error

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

func List(ctx context.Context, dir string) ([]Worktree, error)

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.

Jump to

Keyboard shortcuts

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