Documentation
¶
Overview ¶
Package worktree manages the git worktree behind a bay.
devbay bundles worktree management rather than composing with a separate worktree tool, because a worktree and its runtime have to be created and destroyed as one unit: a worktree removed while its containers still hold a bind mount, or containers removed while the worktree survives, are both leaks that show up later as confusing failures.
It does not, however, insist on owning the worktree. Coding agents now create worktrees natively — Claude Code checks out into .claude/worktrees/ and exposes it to the model directly — so when a worktree for the requested branch already exists, devbay adopts it instead of creating a second checkout of the same branch, which git would refuse anyway.
Every git invocation here is an argv array executed without a shell, for the same reason the manifest requires it: branch names come from users and from agents, and "feat/$(whoami)" must be a branch name, not a command.
Index ¶
- Variables
- type CreateOptions
- type Manager
- func (m *Manager) BranchHasWork(branch string) bool
- func (m *Manager) Create(opts CreateOptions) (*Worktree, error)
- func (m *Manager) DeleteBranch(branch string) error
- func (m *Manager) Find(branch string) (*Worktree, bool, error)
- func (m *Manager) List() ([]*Worktree, error)
- func (m *Manager) Remove(branch string, force bool) error
- type Worktree
Constants ¶
This section is empty.
Variables ¶
var ErrDirty = errors.New("worktree has uncommitted changes")
ErrDirty is returned when removing a worktree would discard uncommitted work.
Functions ¶
This section is empty.
Types ¶
type CreateOptions ¶
type CreateOptions struct {
// Name is the bay name; it becomes the directory name.
Name string
// Branch to check out. Defaults to Name.
Branch string
// From is the starting point for a branch that does not exist yet.
// Defaults to the repository's current HEAD.
From string
}
CreateOptions describes a worktree to create.
type Manager ¶
type Manager struct {
// RepoRoot is the main checkout's root.
RepoRoot string
// Root is where devbay creates new worktrees.
Root string
// Log receives notes a developer needs to see. Never nil after Open.
Log func(format string, args ...any)
// Reclaim takes ownership of a worktree back from the containers that
// wrote into it, and is called only when removal has already failed for
// lack of permission.
//
// A container writing into the bind-mounted worktree writes as whatever
// user it runs as, which for most images is root. On Linux that ownership
// is the host's ownership -- there is no mapping layer -- so a build
// artefact, a lockfile, or a test report left behind by a container is a
// root-owned file the developer cannot delete, and `devbay rm` fails
// having already destroyed the containers. On macOS the file sharing layer
// maps ownership to the calling user and this never happens, which is
// exactly why it went unnoticed until CI ran on Linux.
//
// A function rather than a direct call because this package deliberately
// knows nothing about containers; the caller supplies the means.
Reclaim func(path string) error
}
Manager operates on one repository.
func Open ¶
Open locates the repository containing dir and returns a Manager for it. root is where new worktrees are created; if empty, ~/.devbay/worktrees is used.
func (*Manager) BranchHasWork ¶
BranchHasWork reports whether a branch holds commits that exist nowhere else.
The question teardown needs answered: a branch with no unique commits is bookkeeping and can go with the bay, while one carrying work must survive even though the bay does not. Deleting the second kind would be data loss; keeping the first kind is what makes `devbay rm` followed by `devbay new` silently resurrect an old commit.
func (*Manager) Create ¶
func (m *Manager) Create(opts CreateOptions) (*Worktree, error)
Create makes a worktree for opts.Branch, or adopts an existing one.
Adoption is not a fallback for an error case; it is the expected path when an agent made the worktree first. git refuses to check out one branch into two worktrees, so without adoption devbay could not run a bay on a branch an agent was already working in — which is the common case, not a corner.
func (*Manager) DeleteBranch ¶
DeleteBranch removes a branch, used when unwinding a failed creation.
git worktree remove takes the checkout away but leaves the branch, so without this a failed attempt keeps its branch pointing at whatever was current when it ran -- and the obvious retry checks that stale commit out again, appearing to ignore the fix the caller just made.
type Worktree ¶
type Worktree struct {
// Path is the absolute path of the checkout.
Path string
// Branch is the branch checked out there, without the refs/heads/ prefix.
// Empty for a detached HEAD.
Branch string
// Head is the commit currently checked out.
Head string
// Main reports whether this is the repository's primary checkout rather
// than a linked worktree. devbay never removes the main worktree.
Main bool
// CreatedBranch reports that this call created the branch, rather than
// checking out one that already existed. It matters on failure: unwinding
// has to delete a branch devbay created, or a retry silently checks out
// the stale one instead of the caller's corrected code.
CreatedBranch bool
// Adopted reports that devbay found this worktree rather than creating it —
// typically an agent's own. Adopted worktrees are not removed on teardown,
// because devbay did not create them and something else may still be using
// them.
Adopted bool
}
Worktree is one checkout.