twig

package module
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Jan 30, 2026 License: MIT Imports: 19 Imported by: 0

README

twig

A CLI tool that creates, deletes, and manages git worktrees and branches in a single command. Focused on simplifying git operations, keeping features minimal.

Motivation

twig is designed to be friendly for both humans and agentic coding tools, and to integrate easily with other CLI tools:

  • --quiet minimizes output to paths only, making it easy to pass to other tools
  • For human use, --verbose and interactive confirmations ensure safety

Examples:

cd $(twig add feat/x -q)            # cd into the created worktree
twig list -q | fzf                  # select a worktree with fzf
twig list -q | xargs -I {} code {}  # open all worktrees in VSCode
twig clean -v                       # confirm before deletion, show all skipped items

Design Philosophy

twig treats branches and worktrees as a single unified concept. Users don't need to think about whether they're managing a "branch" or a "worktree" - they simply work with named development contexts.

  • twig add feat/x creates both the branch and worktree together
  • twig remove feat/x deletes both together
  • Even if a worktree directory is deleted externally, twig remove still works

This 1:1 mapping simplifies the mental model: one name, one workspace, one command.

Features

Create worktree and branch in one command

twig add feat/xxx executes worktree creation, branch creation, and symlink setup all at once. Use --source to create from any branch regardless of current worktree. Set default_source in config to always branch from a fixed base (e.g., main).

Create new worktrees with personal settings like .envrc and Claude configs carried over. Git worktree operations don't copy gitignored files, so twig uses symlinks to share these files across worktrees. Start working immediately in new worktrees without manual setup.

Move uncommitted changes to a new branch

Use --carry to move changes to a new worktree, or --sync to copy them to both. Use --file with --carry to move only specific files matching a glob pattern.

Examples:

  • Move refactoring ideas to a separate worktree and continue main work
  • Extract WIP changes to a new branch before switching tasks
Clean up worktrees no longer needed

twig clean removes worktrees that are merged, have upstream gone, or are prunable.

Installation

Requires Git 2.15+.

Homebrew
brew install 708u/tap/twig
Go
go install github.com/708u/twig/cmd/twig@latest

Quick Start

# Initialize settings
twig init

# Create a new worktree and branch
twig add feat/new-feature

# Copy uncommitted changes to a new worktree
twig add feat/wip --sync

# Move uncommitted changes to a new worktree
twig add feat/wip --carry

# List worktrees
twig list

# Clean up worktrees no longer needed
twig clean

# Delete a specific worktree
twig remove feat/done

Configuration

Configure in .twig/settings.toml:

  • worktree_destination_base_dir: Destination directory for worktrees
  • default_source: Source branch for symlinks (creates symlinks from main even when adding from a derived worktree)
  • symlinks: Glob patterns for symlink targets
  • init_submodules: Initialize submodules when creating worktrees

Personal settings can be overridden in .twig/settings.local.toml (.gitignore recommended).

  • extra_symlinks: Add personal patterns while preserving team settings

Details: docs/reference/configuration.md

Shell Completion

Shell completion is available for all commands and flags. For example, twig remove <TAB> completes existing branch names.

Add the following to your shell configuration:

Bash

Add to ~/.bashrc:

eval "$(twig completion bash)"
Zsh

Add to ~/.zshrc:

eval "$(twig completion zsh)"
Fish

Add to ~/.config/fish/config.fish:

twig completion fish | source

Command Specs

Command Description
init Initialize settings
add Create worktree and branch
list List worktrees
remove Delete worktree and branch (multiple supported)
clean Bulk delete merged worktrees
sync Sync symlinks and submodules to worktrees

See the documentation above for detailed flags and specifications.

Claude Code Plugin

A Claude Code plugin is available for AI-assisted worktree management. The plugin provides an agent and skill that help Claude understand twig commands and execute worktree operations.

Plugin Installation

Run the following slash commands in a Claude Code session:

# Add the marketplace
/plugin marketplace add 708u/twig

# Install the plugin
/plugin install twig@708u-twig
What the Plugin Provides
Component Description
twig-operator agent Executes twig commands based on user intent
twig-guide skill Provides command syntax and usage details
Usage Examples

Once installed, Claude can help with worktree operations:

  • "Create a new worktree for feat/user-auth"
  • "Move my current changes to a new branch"
  • "Clean up merged worktrees"
  • "I want to work on the API and frontend in parallel"

License

MIT

Documentation

Index

Constants

View Source
const (
	GitCmdWorktree   = "worktree"
	GitCmdBranch     = "branch"
	GitCmdStash      = "stash"
	GitCmdStatus     = "status"
	GitCmdRevParse   = "rev-parse"
	GitCmdDiff       = "diff"
	GitCmdFetch      = "fetch"
	GitCmdForEachRef = "for-each-ref"
)

Git command names.

View Source
const (
	GitWorktreeAdd    = "add"
	GitWorktreeRemove = "remove"
	GitWorktreeList   = "list"
	GitWorktreePrune  = "prune"
)

Git worktree subcommands.

View Source
const (
	GitStashPush  = "push"
	GitStashApply = "apply"
	GitStashDrop  = "drop"
	GitStashList  = "list"
)

Git stash subcommands.

View Source
const (
	PorcelainWorktreePrefix = "worktree "
	PorcelainHEADPrefix     = "HEAD "
	PorcelainBranchPrefix   = "branch refs/heads/"
	PorcelainDetached       = "detached"
	PorcelainBare           = "bare"
	PorcelainLocked         = "locked"
	PorcelainPrunable       = "prunable"
)

Porcelain output format prefixes and values.

View Source
const (
	LogCategoryDebug  = "debug"
	LogCategoryGit    = "git"
	LogCategoryConfig = "config"
	LogCategoryGlob   = "glob"
	LogCategoryRemove = "remove"
	LogCategoryClean  = "clean"
	LogCategorySync   = "sync"
)

Log category values for consistent output prefixes.

View Source
const (
	// DefaultCommandIDBytes is the number of random bytes for command ID generation.
	// This produces an 8-character hex string (4 bytes = 8 hex chars).
	DefaultCommandIDBytes = 4
)

Command ID generation settings.

View Source
const GitCmdSubmodule = "submodule"

GitCmdSubmodule is the git submodule command.

View Source
const (
	GitSubmoduleUpdate = "update"
)

Git submodule subcommands.

View Source
const RefsHeadsPrefix = "refs/heads/"

RefsHeadsPrefix is the git refs prefix for local branches.

Variables

This section is empty.

Functions

func GenerateCommandID added in v0.8.0

func GenerateCommandID() string

GenerateCommandID generates a random command ID for log grouping. Returns an 8-character hex string (e.g., "a1b2c3d4").

func GenerateCommandIDWithLength added in v0.8.0

func GenerateCommandIDWithLength(byteLen int) string

GenerateCommandIDWithLength generates a command ID with the specified byte length. The returned string is hex-encoded, so it has 2*byteLen characters.

func IsColorEnabled added in v0.14.0

func IsColorEnabled() bool

IsColorEnabled returns whether color output is enabled. This should be called after SetColorMode.

func NewNopLogger added in v0.7.0

func NewNopLogger() *slog.Logger

NewNopLogger creates a logger that discards all output. Used as the default logger when no logging is needed.

func SetColorMode added in v0.14.0

func SetColorMode(mode ColorMode)

SetColorMode configures color output based on mode.

func VerbosityToLevel added in v0.7.0

func VerbosityToLevel(verbosity int) slog.Level

VerbosityToLevel converts a verbosity count to a slog.Level.

0 (no flag): LevelWarn - errors and warnings only
1 (-v):      LevelInfo - detailed results
2+ (-vv):    LevelDebug - trace output

Types

type AddCommand

type AddCommand struct {
	FS                 FileSystem
	Git                *GitRunner
	Config             *Config
	Log                *slog.Logger
	Sync               bool
	CarryFrom          string
	FilePatterns       []string
	Lock               bool
	LockReason         string
	InitSubmodules     bool
	SubmoduleReference bool
}

AddCommand creates git worktrees with symlinks.

func NewAddCommand

func NewAddCommand(fs FileSystem, git *GitRunner, cfg *Config, log *slog.Logger, opts AddOptions) *AddCommand

NewAddCommand creates an AddCommand with explicit dependencies (for testing).

func NewDefaultAddCommand

func NewDefaultAddCommand(cfg *Config, log *slog.Logger, opts AddOptions) *AddCommand

NewDefaultAddCommand creates an AddCommand with production defaults.

func (*AddCommand) Run

func (c *AddCommand) Run(ctx context.Context, name string) (AddResult, error)

Run creates a new worktree for the given branch name.

type AddFormatOptions

type AddFormatOptions struct {
	Verbose bool
	Quiet   bool
}

AddFormatOptions configures add output formatting.

type AddOptions

type AddOptions struct {
	Sync               bool
	CarryFrom          string   // empty: no carry, non-empty: resolved path to carry from
	FilePatterns       []string // file patterns to carry (empty means all files)
	Lock               bool
	LockReason         string
	InitSubmodules     bool
	SubmoduleReference bool
}

AddOptions holds options for the add command.

type AddResult

type AddResult struct {
	Branch         string
	WorktreePath   string
	Symlinks       []SymlinkResult
	GitOutput      []byte
	ChangesSynced  bool
	ChangesCarried bool
	SubmoduleInit  SubmoduleInitResult
}

AddResult holds the result of an add operation.

func (AddResult) Format

func (r AddResult) Format(opts AddFormatOptions) FormatResult

Format formats the AddResult for display.

type BranchDeleteOption

type BranchDeleteOption func(*branchDeleteOptions)

BranchDeleteOption is a functional option for BranchDelete.

func WithForceDelete

func WithForceDelete() BranchDeleteOption

WithForceDelete forces branch deletion even if not fully merged.

type BranchMergeStatus added in v0.9.0

type BranchMergeStatus struct {
	// Merged contains branches that are considered merged
	// (via git branch --merged or upstream gone, excluding same-commit branches).
	Merged map[string]bool
	// SameCommit contains branches pointing to the same commit as target.
	// These are excluded from Merged because they could be newly created or ff-merged.
	SameCommit map[string]bool
}

BranchMergeStatus holds the classification of branches by merge status.

type CLIHandler added in v0.7.0

type CLIHandler struct {
	// contains filtered or unexported fields
}

CLIHandler is a slog.Handler that outputs plain text for CLI usage. Format: "2006-01-02 15:04:05.000 [LEVEL] [cmd_id] category: message"

func NewCLIHandler added in v0.7.0

func NewCLIHandler(w io.Writer, level slog.Level) *CLIHandler

NewCLIHandler creates a new CLIHandler that writes to w. Only messages at or above the specified level are output.

func (*CLIHandler) Enabled added in v0.7.0

func (h *CLIHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled reports whether the handler handles records at the given level.

func (*CLIHandler) Handle added in v0.7.0

func (h *CLIHandler) Handle(ctx context.Context, r slog.Record) error

Handle writes a log record to the handler's writer. Format: 2006-01-02 15:04:05.000 [LEVEL] [cmd_id] category: message

func (*CLIHandler) WithAttrs added in v0.7.0

func (h *CLIHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new handler with the given attributes. The cmd_id attribute is stored separately for efficient access.

func (*CLIHandler) WithGroup added in v0.7.0

func (h *CLIHandler) WithGroup(_ string) slog.Handler

WithGroup returns a new handler with the given group name. Currently not implemented: group is ignored.

type CheckOptions added in v0.6.0

type CheckOptions struct {
	Force        WorktreeForceLevel // Force level to apply
	Target       string             // Target branch for merged check (empty = skip merged check)
	Cwd          string             // Current directory for cwd check
	WorktreeInfo *Worktree          // Pre-fetched worktree info (skips WorktreeFindByBranch if set)
	MergeStatus  BranchMergeStatus  // Pre-fetched branch merge status (skips IsBranchMerged if set)
}

CheckOptions configures the check operation.

type CheckResult added in v0.6.0

type CheckResult struct {
	CanRemove    bool         // Whether the worktree can be removed
	SkipReason   SkipReason   // Reason if cannot be removed
	CleanReason  CleanReason  // Reason if can be removed (for clean command display)
	Prunable     bool         // Whether worktree is prunable (directory was deleted externally)
	WorktreePath string       // Path to the worktree
	Branch       string       // Branch name
	ChangedFiles []FileStatus // Uncommitted changes (for verbose output)
}

CheckResult holds the result of checking whether a worktree can be removed.

type CleanCandidate

type CleanCandidate struct {
	Branch       string
	WorktreePath string
	Prunable     bool
	Skipped      bool
	SkipReason   SkipReason
	CleanReason  CleanReason
	ChangedFiles []FileStatus
}

CleanCandidate represents a worktree that can be cleaned.

type CleanCommand

type CleanCommand struct {
	FS     FileSystem
	Git    *GitRunner
	Config *Config
	Log    *slog.Logger
}

CleanCommand removes merged worktrees that are no longer needed.

func NewCleanCommand

func NewCleanCommand(fs FileSystem, git *GitRunner, cfg *Config, log *slog.Logger) *CleanCommand

NewCleanCommand creates a new CleanCommand with explicit dependencies. Use this for testing or when custom dependencies are needed.

func NewDefaultCleanCommand

func NewDefaultCleanCommand(cfg *Config, log *slog.Logger) *CleanCommand

NewDefaultCleanCommand creates a new CleanCommand with production dependencies.

func (*CleanCommand) Run

func (c *CleanCommand) Run(ctx context.Context, cwd string, opts CleanOptions) (CleanResult, error)

Run analyzes worktrees and optionally removes them. cwd is the current working directory (absolute path) passed from CLI layer.

type CleanOptions

type CleanOptions struct {
	Yes     bool               // Execute without confirmation
	Check   bool               // Show candidates only (no prompt)
	Target  string             // Target branch for merge check (auto-detect if empty)
	Verbose bool               // Show skip reasons
	Force   WorktreeForceLevel // Force level: -f for unclean, -ff for locked
}

CleanOptions configures the clean operation.

type CleanReason added in v0.2.0

type CleanReason string

CleanReason describes why a branch is cleanable.

const (
	CleanMerged       CleanReason = "merged"
	CleanUpstreamGone CleanReason = "upstream gone"
)

type CleanResult

type CleanResult struct {
	Candidates   []CleanCandidate
	Removed      []RemovedWorktree
	TargetBranch string
	Pruned       bool
	Check        bool // --check mode (show candidates only, no prompt)
}

CleanResult aggregates results from clean operations.

func (CleanResult) CleanableCount

func (r CleanResult) CleanableCount() int

CleanableCount returns the number of worktrees that can be cleaned.

func (CleanResult) Format

func (r CleanResult) Format(opts FormatOptions) FormatResult

Format formats the CleanResult for display.

type ColorMode added in v0.14.0

type ColorMode string

ColorMode defines color output behavior.

const (
	ColorModeAuto   ColorMode = "auto"   // Color when TTY
	ColorModeAlways ColorMode = "always" // Always color
	ColorModeNever  ColorMode = "never"  // No color
)

type Config

type Config struct {
	Symlinks            []string `toml:"symlinks"`
	ExtraSymlinks       []string `toml:"extra_symlinks"`
	WorktreeDestBaseDir string   `toml:"worktree_destination_base_dir"`
	DefaultSource       string   `toml:"default_source"`
	WorktreeSourceDir   string   // Set by LoadConfig to the config load directory
	InitSubmodules      *bool    `toml:"init_submodules"`     // nil=unset, true=enable, false=disable
	SubmoduleReference  *bool    `toml:"submodule_reference"` // nil=unset, true=enable, false=disable
}

Config holds the merged configuration for the application. All path fields are resolved to absolute paths by LoadConfig.

func (*Config) ShouldInitSubmodules added in v0.6.0

func (c *Config) ShouldInitSubmodules() bool

ShouldInitSubmodules returns whether submodule initialization is enabled.

func (*Config) ShouldUseSubmoduleReference added in v0.13.0

func (c *Config) ShouldUseSubmoduleReference() bool

ShouldUseSubmoduleReference returns whether to use --reference for submodule init.

type FileStatus added in v0.7.0

type FileStatus struct {
	Status string // e.g., " M", "A ", "??"
	Path   string
}

FileStatus represents a file with its git status.

type FileSystem

type FileSystem interface {
	Stat(name string) (fs.FileInfo, error)
	Lstat(name string) (fs.FileInfo, error)
	Symlink(oldname, newname string) error
	IsNotExist(err error) bool
	Glob(dir, pattern string) ([]string, error)
	MkdirAll(path string, perm fs.FileMode) error
	ReadDir(name string) ([]os.DirEntry, error)
	Remove(name string) error
	WriteFile(name string, data []byte, perm fs.FileMode) error
}

FileSystem abstracts filesystem operations for testability.

type FormatOptions

type FormatOptions struct {
	Verbose      bool
	ColorEnabled bool // Enable color output (--color=auto/always)
}

FormatOptions configures output formatting.

type FormatResult

type FormatResult struct {
	Stdout string
	Stderr string
}

FormatResult holds formatted output strings.

type Formatter

type Formatter interface {
	Format(opts FormatOptions) FormatResult
}

Formatter formats command results.

type GitError

type GitError struct {
	Op     GitOp
	Stderr string
	Err    error
}

GitError represents an error from a git operation with structured information.

func (*GitError) Error

func (e *GitError) Error() string

func (*GitError) Unwrap

func (e *GitError) Unwrap() error

type GitExecutor

type GitExecutor interface {
	// Run executes git with args and returns stdout.
	Run(ctx context.Context, args ...string) ([]byte, error)
}

GitExecutor abstracts git command execution for testability. Commands are fixed to "git" - only subcommands and args are passed.

type GitOp

type GitOp int

GitOp represents the type of git operation.

const (
	OpWorktreeRemove GitOp = iota + 1
	OpBranchDelete
)

func (GitOp) String

func (op GitOp) String() string

type GitRunner

type GitRunner struct {
	Executor GitExecutor
	Dir      string
	Log      *slog.Logger
}

GitRunner provides git operations using GitExecutor.

func NewGitRunner

func NewGitRunner(dir string, opts ...GitRunnerOption) *GitRunner

NewGitRunner creates a new GitRunner with the default executor.

func (*GitRunner) BranchDelete

func (g *GitRunner) BranchDelete(ctx context.Context, branch string, opts ...BranchDeleteOption) ([]byte, error)

BranchDelete deletes a local branch. By default uses -d (safe delete). Use WithForceDelete() to use -D (force delete).

func (*GitRunner) BranchList

func (g *GitRunner) BranchList(ctx context.Context) ([]string, error)

BranchList returns all local branch names.

func (*GitRunner) ChangedFiles

func (g *GitRunner) ChangedFiles(ctx context.Context) ([]FileStatus, error)

ChangedFiles returns files with uncommitted changes including staged, unstaged, and untracked files. Status codes are the first 2 characters from git status --porcelain output.

func (*GitRunner) CheckSubmoduleCleanStatus added in v0.6.0

func (g *GitRunner) CheckSubmoduleCleanStatus(ctx context.Context) (SubmoduleCleanStatus, error)

CheckSubmoduleCleanStatus determines if it's safe to remove a worktree with submodules. Returns:

  • SubmoduleCleanStatusNone: no initialized submodules
  • SubmoduleCleanStatusClean: submodules exist but are clean (safe to auto-force)
  • SubmoduleCleanStatusDirty: submodules have changes (requires user --force)

func (*GitRunner) ClassifyBranchMergeStatus added in v0.9.0

func (g *GitRunner) ClassifyBranchMergeStatus(ctx context.Context, target string) (BranchMergeStatus, error)

ClassifyBranchMergeStatus classifies branches by their merge status relative to target. A branch is merged if it's in `git branch --merged <target>` or if its upstream is gone. Branches pointing to the same commit as target are returned separately in SameCommit. This is more efficient than calling IsBranchMerged for each branch individually.

func (*GitRunner) Fetch added in v0.3.0

func (g *GitRunner) Fetch(ctx context.Context, remote string, refspec ...string) error

Fetch fetches the specified refspec from the remote.

func (*GitRunner) FindRemoteForBranch added in v0.3.0

func (g *GitRunner) FindRemoteForBranch(ctx context.Context, branch string) (string, error)

FindRemoteForBranch finds the remote that has the specified branch. Returns the remote name if exactly one remote has the branch. Returns empty string if no remote has the branch. Returns error if multiple remotes have the branch (ambiguous).

func (*GitRunner) FindRemotesForBranch added in v0.3.0

func (g *GitRunner) FindRemotesForBranch(ctx context.Context, branch string) ([]string, error)

FindRemotesForBranch returns all remotes that have the specified branch in local remote-tracking branches. This checks refs/remotes/*/<branch> locally without network access.

func (*GitRunner) HasChanges

func (g *GitRunner) HasChanges(ctx context.Context) (bool, error)

HasChanges checks if there are any uncommitted changes (staged, unstaged, or untracked).

func (*GitRunner) InDir

func (g *GitRunner) InDir(dir string) *GitRunner

InDir returns a GitRunner that executes commands in the specified directory.

func (*GitRunner) IsBranchMerged

func (g *GitRunner) IsBranchMerged(ctx context.Context, branch, target string) (bool, error)

IsBranchMerged checks if branch is merged into target.

func (*GitRunner) IsBranchUpstreamGone added in v0.2.0

func (g *GitRunner) IsBranchUpstreamGone(ctx context.Context, branch string) (bool, error)

IsBranchUpstreamGone checks if the branch's upstream tracking branch is gone. This indicates the remote branch was deleted, typically after a PR merge.

func (*GitRunner) LocalBranchExists added in v0.3.0

func (g *GitRunner) LocalBranchExists(ctx context.Context, branch string) (bool, error)

LocalBranchExists checks if a branch exists in the local repository.

func (*GitRunner) MainWorktreePath added in v0.13.0

func (g *GitRunner) MainWorktreePath(ctx context.Context) (string, error)

MainWorktreePath returns the path of the main worktree. Uses git rev-parse --git-common-dir which returns the shared .git directory.

func (*GitRunner) Run

func (g *GitRunner) Run(ctx context.Context, args ...string) ([]byte, error)

Run executes git command with -C flag.

func (*GitRunner) StashApplyByHash

func (g *GitRunner) StashApplyByHash(ctx context.Context, hash string) ([]byte, error)

StashApplyByHash applies the stash with the given hash without dropping it.

func (*GitRunner) StashDropByHash

func (g *GitRunner) StashDropByHash(ctx context.Context, hash string) ([]byte, error)

StashDropByHash drops the stash with the given hash.

func (*GitRunner) StashPopByHash

func (g *GitRunner) StashPopByHash(ctx context.Context, hash string) ([]byte, error)

StashPopByHash applies and drops the stash with the given hash.

func (*GitRunner) StashPush

func (g *GitRunner) StashPush(ctx context.Context, message string, pathspecs ...string) (string, error)

StashPush stashes changes including untracked files. If pathspecs are provided, only matching files are stashed. Returns the stash commit hash for later reference.

Race condition note: There is a small race window between "stash push" and "rev-parse stash@{0}". If another process creates a stash in this window, we may get the wrong hash. However, this window is very small (milliseconds) and acceptable in practice.

Why not use "stash create" + "stash store" pattern? "stash create" does not support -u/--include-untracked option (git limitation). It can only stash tracked file changes, not untracked files. See: https://git-scm.com/docs/git-stash

func (*GitRunner) SubmoduleStatus added in v0.6.0

func (g *GitRunner) SubmoduleStatus(ctx context.Context) ([]SubmoduleInfo, error)

SubmoduleStatus runs `git submodule status --recursive` and parses the output. Returns a list of SubmoduleInfo for all submodules.

func (*GitRunner) SubmoduleUpdate added in v0.6.0

func (g *GitRunner) SubmoduleUpdate(ctx context.Context, opts ...SubmoduleUpdateOption) (SubmoduleUpdateResult, error)

SubmoduleUpdate runs git submodule update --init. With WithSubmoduleReference, uses --reference for faster initialization.

func (*GitRunner) WorktreeAdd

func (g *GitRunner) WorktreeAdd(ctx context.Context, path, branch string, opts ...WorktreeAddOption) ([]byte, error)

WorktreeAdd creates a new worktree at the specified path.

func (*GitRunner) WorktreeFindByBranch

func (g *GitRunner) WorktreeFindByBranch(ctx context.Context, branch string) (*Worktree, error)

WorktreeFindByBranch returns the Worktree for the given branch. Returns an error if the branch is not checked out in any worktree.

func (*GitRunner) WorktreeList

func (g *GitRunner) WorktreeList(ctx context.Context) ([]Worktree, error)

WorktreeList returns all worktrees with their paths and branches.

func (*GitRunner) WorktreeListBranches

func (g *GitRunner) WorktreeListBranches(ctx context.Context) ([]string, error)

WorktreeListBranches returns a list of branch names currently checked out in worktrees.

func (*GitRunner) WorktreePrune

func (g *GitRunner) WorktreePrune(ctx context.Context) ([]byte, error)

WorktreePrune removes references to worktrees that no longer exist.

func (*GitRunner) WorktreeRemove

func (g *GitRunner) WorktreeRemove(ctx context.Context, path string, opts ...WorktreeRemoveOption) ([]byte, error)

WorktreeRemove removes the worktree at the given path. By default fails if there are uncommitted changes. Use WithForceRemove() to force.

func (*GitRunner) WorktreeRoot added in v0.12.0

func (g *GitRunner) WorktreeRoot(ctx context.Context) (string, error)

WorktreeRoot returns the root path of the worktree containing the current directory. Uses git rev-parse --show-toplevel which returns the path git internally uses.

type GitRunnerOption added in v0.9.0

type GitRunnerOption func(*gitRunnerOptions)

GitRunnerOption configures GitRunner.

func WithLogger added in v0.9.0

func WithLogger(log *slog.Logger) GitRunnerOption

WithLogger sets the logger for GitRunner.

type InitCommand

type InitCommand struct {
	FS  FileSystem
	Log *slog.Logger
}

InitCommand initializes twig configuration in a directory.

func NewDefaultInitCommand

func NewDefaultInitCommand(log *slog.Logger) *InitCommand

NewDefaultInitCommand creates an InitCommand with production defaults.

func NewInitCommand

func NewInitCommand(fs FileSystem, log *slog.Logger) *InitCommand

NewInitCommand creates an InitCommand with explicit dependencies (for testing).

func (*InitCommand) Run

func (c *InitCommand) Run(ctx context.Context, dir string, opts InitOptions) (InitResult, error)

Run executes the init command.

type InitFormatOptions

type InitFormatOptions struct {
	Verbose bool
}

InitFormatOptions holds formatting options for InitResult.

type InitOptions

type InitOptions struct {
	Force bool
}

InitOptions holds options for the init command.

type InitResult

type InitResult struct {
	ConfigDir    string
	SettingsPath string
	Created      bool
	Skipped      bool
	Overwritten  bool
}

InitResult holds the result of the init command.

func (InitResult) Format

func (r InitResult) Format(opts InitFormatOptions) FormatResult

Format formats the result for output.

type ListCommand

type ListCommand struct {
	Git *GitRunner
	Log *slog.Logger
}

ListCommand lists all worktrees.

func NewDefaultListCommand

func NewDefaultListCommand(dir string, log *slog.Logger) *ListCommand

NewDefaultListCommand creates a ListCommand with production defaults.

func NewListCommand

func NewListCommand(git *GitRunner, log *slog.Logger) *ListCommand

NewListCommand creates a ListCommand with explicit dependencies (for testing).

func (*ListCommand) Run

func (c *ListCommand) Run(ctx context.Context) (ListResult, error)

Run lists all worktrees.

type ListFormatOptions

type ListFormatOptions struct {
	Quiet bool
}

ListFormatOptions configures list output formatting.

type ListResult

type ListResult struct {
	Worktrees []Worktree
}

ListResult holds the result of a list operation.

func (ListResult) Format

func (r ListResult) Format(opts ListFormatOptions) FormatResult

Format formats the ListResult for display.

type LoadConfigResult

type LoadConfigResult struct {
	Config   *Config
	Warnings []string
}

LoadConfigResult contains the loaded config and any warnings.

func LoadConfig

func LoadConfig(dir string) (*LoadConfigResult, error)

type LogAttrKey added in v0.8.0

type LogAttrKey string

LogAttrKey is a type-safe key for slog attributes.

const (
	LogAttrKeyCategory LogAttrKey = "category"
	LogAttrKeyCmdID    LogAttrKey = "cmd_id"
)

Log attribute keys for slog records.

func (LogAttrKey) Attr added in v0.8.0

func (k LogAttrKey) Attr(value string) slog.Attr

Attr creates a slog.Attr with this key and the given value.

func (LogAttrKey) String added in v0.8.0

func (k LogAttrKey) String() string

String returns the string value of the key.

type RemoveCommand

type RemoveCommand struct {
	FS     FileSystem
	Git    *GitRunner
	Config *Config
	Log    *slog.Logger
}

RemoveCommand removes git worktrees with their associated branches.

func NewDefaultRemoveCommand

func NewDefaultRemoveCommand(cfg *Config, log *slog.Logger) *RemoveCommand

NewDefaultRemoveCommand creates a RemoveCommand with production defaults.

func NewRemoveCommand

func NewRemoveCommand(fs FileSystem, git *GitRunner, cfg *Config, log *slog.Logger) *RemoveCommand

NewRemoveCommand creates a RemoveCommand with explicit dependencies.

func (*RemoveCommand) Check added in v0.6.0

func (c *RemoveCommand) Check(ctx context.Context, branch string, opts CheckOptions) (CheckResult, error)

Check checks whether a worktree can be removed based on the given options. This method does not modify any state.

func (*RemoveCommand) Run

func (c *RemoveCommand) Run(ctx context.Context, branch string, cwd string, opts RemoveOptions) (RemovedWorktree, error)

Run removes the worktree and branch for the given branch name. cwd is used to prevent removal when inside the target worktree.

type RemoveOptions

type RemoveOptions struct {
	// Force specifies the force level.
	// Matches git worktree behavior: -f for unclean, -f -f for locked.
	Force WorktreeForceLevel
	Check bool // Show what would be removed without making changes
}

RemoveOptions configures the remove operation.

type RemoveResult

type RemoveResult struct {
	Removed []RemovedWorktree
}

RemoveResult aggregates results from remove operations.

func (RemoveResult) ErrorCount

func (r RemoveResult) ErrorCount() int

ErrorCount returns the number of failed removals.

func (RemoveResult) Format

func (r RemoveResult) Format(opts FormatOptions) FormatResult

Format formats the RemoveResult for display.

func (RemoveResult) HasErrors

func (r RemoveResult) HasErrors() bool

HasErrors returns true if any errors occurred.

type RemovedWorktree

type RemovedWorktree struct {
	Branch       string
	WorktreePath string
	CleanedDirs  []string     // Empty parent directories that were removed
	Pruned       bool         // Stale worktree record was pruned (directory was already deleted)
	Check        bool         // --check mode: show what would be removed
	CanRemove    bool         // Whether the worktree can be removed (from Check)
	SkipReason   SkipReason   // Reason if cannot be removed (from Check)
	ChangedFiles []FileStatus // Uncommitted changes (for verbose output)
	GitOutput    []byte
	Err          error // nil if success
}

RemovedWorktree holds the result of a single worktree removal.

func (RemovedWorktree) Format

Format formats the RemovedWorktree for display.

type SkipError added in v0.6.0

type SkipError struct {
	Reason SkipReason
}

SkipError represents an error when a worktree cannot be removed due to a skip condition.

func (*SkipError) Error added in v0.6.0

func (e *SkipError) Error() string

type SkipReason

type SkipReason string

SkipReason describes why a worktree was skipped.

const (
	SkipNotMerged      SkipReason = "not merged"
	SkipSameCommit     SkipReason = "same commit"
	SkipHasChanges     SkipReason = "has uncommitted changes"
	SkipLocked         SkipReason = "locked"
	SkipCurrentDir     SkipReason = "current directory"
	SkipDetached       SkipReason = "detached HEAD"
	SkipDirtySubmodule SkipReason = "submodule has uncommitted changes"
)

func (SkipReason) Format added in v0.13.0

func (r SkipReason) Format(target string) string

Format returns the display string for a SkipReason. For SkipSameCommit, it appends the target branch name.

type SubmoduleCleanStatus added in v0.6.0

type SubmoduleCleanStatus int

SubmoduleCleanStatus indicates whether it's safe to remove a worktree with submodules.

const (
	// SubmoduleCleanStatusNone: no initialized submodules exist.
	SubmoduleCleanStatusNone SubmoduleCleanStatus = iota
	// SubmoduleCleanStatusClean: submodules exist but all are clean.
	SubmoduleCleanStatusClean
	// SubmoduleCleanStatusDirty: submodules have uncommitted changes or are at different commits.
	SubmoduleCleanStatusDirty
)

type SubmoduleInfo added in v0.6.0

type SubmoduleInfo struct {
	SHA   string
	Path  string
	State SubmoduleState
}

SubmoduleInfo holds information about a submodule.

type SubmoduleInitResult added in v0.6.0

type SubmoduleInitResult struct {
	Attempted             bool     // true if initialization was attempted
	Count                 int      // number of initialized submodules
	Skipped               bool     // true if initialization failed
	Reason                string   // reason for failure (warning message)
	NoReferenceSubmodules []string // submodules that couldn't use reference
}

SubmoduleInitResult holds information about submodule initialization.

type SubmoduleState added in v0.6.0

type SubmoduleState int

SubmoduleState represents the state of a submodule.

const (
	SubmoduleStateUninitialized SubmoduleState = iota
	SubmoduleStateClean
	SubmoduleStateModified
	SubmoduleStateConflict
)

type SubmoduleUpdateOption added in v0.13.0

type SubmoduleUpdateOption func(*submoduleUpdateOptions)

SubmoduleUpdateOption configures SubmoduleUpdate behavior.

func WithSubmoduleReference added in v0.13.0

func WithSubmoduleReference(mainWorktreePath string) SubmoduleUpdateOption

WithSubmoduleReference enables --reference optimization using main worktree's modules.

type SubmoduleUpdateResult added in v0.13.0

type SubmoduleUpdateResult struct {
	Count       int      // number of initialized submodules
	NoReference []string // submodules that couldn't use reference
}

SubmoduleUpdateResult holds the result of SubmoduleUpdate.

type SymlinkResult

type SymlinkResult struct {
	Src     string
	Dst     string
	Skipped bool
	Reason  string
}

SymlinkResult holds information about a symlink operation.

type SyncCommand added in v0.10.0

type SyncCommand struct {
	FS  FileSystem
	Git *GitRunner
	Log *slog.Logger
}

SyncCommand syncs symlinks and submodules from source worktree to target worktrees.

func NewDefaultSyncCommand added in v0.10.0

func NewDefaultSyncCommand(gitDir string, log *slog.Logger) *SyncCommand

NewDefaultSyncCommand creates a SyncCommand with production defaults.

func NewSyncCommand added in v0.10.0

func NewSyncCommand(fs FileSystem, git *GitRunner, log *slog.Logger) *SyncCommand

NewSyncCommand creates a SyncCommand with explicit dependencies.

func (*SyncCommand) Run added in v0.10.0

func (c *SyncCommand) Run(ctx context.Context, targets []string, cwd string, opts SyncOptions) (SyncResult, error)

Run syncs symlinks and submodules from source to target worktrees.

type SyncFormatOptions added in v0.10.0

type SyncFormatOptions struct {
	Verbose bool
	Quiet   bool
}

SyncFormatOptions configures sync output formatting.

type SyncOptions added in v0.10.0

type SyncOptions struct {
	Check              bool     // Show what would be synced (dry-run)
	All                bool     // Sync all worktrees
	Source             string   // Source branch
	SourcePath         string   // Source worktree path
	Symlinks           []string // Symlink patterns from source config
	InitSubmodules     bool     // Whether to init submodules from source config
	SubmoduleReference bool     // Whether to use --reference for submodule init
	Verbose            bool     // Verbose output
}

SyncOptions configures the sync operation.

type SyncResult added in v0.10.0

type SyncResult struct {
	Targets       []SyncTargetResult
	SourceBranch  string
	Check         bool // --check mode
	NothingToSync bool // No symlinks or submodules configured
}

SyncResult aggregates results from sync operations.

func (SyncResult) ErrorCount added in v0.10.0

func (r SyncResult) ErrorCount() int

ErrorCount returns the number of failed targets.

func (SyncResult) Format added in v0.10.0

func (r SyncResult) Format(opts SyncFormatOptions) FormatResult

Format formats the SyncResult for display.

func (SyncResult) HasErrors added in v0.10.0

func (r SyncResult) HasErrors() bool

HasErrors returns true if any errors occurred.

func (SyncResult) SkippedCount added in v0.10.0

func (r SyncResult) SkippedCount() int

SkippedCount returns the number of skipped targets.

func (SyncResult) SuccessCount added in v0.10.0

func (r SyncResult) SuccessCount() int

SuccessCount returns the number of successfully synced targets.

func (SyncResult) SyncedBranches added in v0.10.0

func (r SyncResult) SyncedBranches() []string

SyncedBranches returns the list of successfully synced branch names.

type SyncTargetResult added in v0.10.0

type SyncTargetResult struct {
	Branch        string
	WorktreePath  string
	Symlinks      []SymlinkResult
	SubmoduleInit SubmoduleInitResult
	Skipped       bool
	SkipReason    string
	Err           error
}

SyncTargetResult holds the result of syncing a single worktree.

type Worktree added in v0.2.0

type Worktree struct {
	Path           string
	Branch         string
	HEAD           string
	Detached       bool
	Locked         bool
	LockReason     string
	Prunable       bool
	PrunableReason string
	Bare           bool
}

Worktree holds worktree path and branch information.

func (Worktree) ShortHEAD added in v0.2.0

func (w Worktree) ShortHEAD() string

ShortHEAD returns the first 7 characters of the HEAD commit hash.

type WorktreeAddOption

type WorktreeAddOption func(*worktreeAddOptions)

WorktreeAddOption is a functional option for WorktreeAdd.

func WithCreateBranch

func WithCreateBranch() WorktreeAddOption

WithCreateBranch creates a new branch when adding the worktree.

func WithLock

func WithLock() WorktreeAddOption

WithLock locks the worktree after creation.

func WithLockReason

func WithLockReason(reason string) WorktreeAddOption

WithLockReason sets the reason for locking the worktree.

type WorktreeForceLevel

type WorktreeForceLevel uint8

WorktreeForceLevel represents the force level for worktree removal. Matches git worktree remove behavior.

const (
	// WorktreeForceLevelNone means no force - fail on uncommitted changes or locked.
	WorktreeForceLevelNone WorktreeForceLevel = iota
	// WorktreeForceLevelUnclean removes unclean worktrees (-f).
	WorktreeForceLevelUnclean
	// WorktreeForceLevelLocked also removes locked worktrees (-f -f).
	WorktreeForceLevelLocked
)

type WorktreeRemoveOption

type WorktreeRemoveOption func(*worktreeRemoveOptions)

WorktreeRemoveOption is a functional option for WorktreeRemove.

func WithForceRemove

func WithForceRemove(level WorktreeForceLevel) WorktreeRemoveOption

WithForceRemove forces worktree removal.

Directories

Path Synopsis
cmd
twig command
internal

Jump to

Keyboard shortcuts

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