worktree

package
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func Open(dir, root string) (*Manager, error)

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

func (m *Manager) BranchHasWork(branch string) bool

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

func (m *Manager) DeleteBranch(branch string) error

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.

func (*Manager) Find

func (m *Manager) Find(branch string) (*Worktree, bool, error)

Find returns the worktree holding branch, if any.

func (*Manager) List

func (m *Manager) List() ([]*Worktree, error)

List returns every worktree git knows about, main checkout included.

func (*Manager) Prune added in v0.3.0

func (m *Manager) Prune() error

Prune reconciles git's record with what is on disk.

Exported because a caller that removes a worktree directory itself -- the remains of an interrupted create, which git never registered -- leaves git's administrative files behind, and the next create fails for a different reason than the first.

func (*Manager) Remove

func (m *Manager) Remove(branch string, force bool) error

Remove deletes a worktree devbay created. It refuses to discard uncommitted work unless force is set, and never removes the main checkout or a worktree devbay adopted.

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.

Jump to

Keyboard shortcuts

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