Skip to content

Latest commit

 

History

History
119 lines (96 loc) · 7.13 KB

File metadata and controls

119 lines (96 loc) · 7.13 KB

git-wt — Agent / Contributor Notes

A Go CLI for managing git worktrees: create, navigate, list, multi-select remove, clean stale worktrees, generate a shell-integration wrapper.

End-user docs live in README.md. This file is the working-copy reference for anyone (human or agent) modifying the codebase.

Stack

  • Language: Go 1.22+ (module github.com/shhac/git-wt)
  • CLI framework: Cobra
  • Picker: custom bubbletea components in internal/picker/ (we tried huh initially but its option- rendering pipeline strips per-column ANSI; built our own to keep full control)
  • Styling: lipgloss
  • TTY detection: mattn/go-isatty

Layout

cmd/git-wt/main.go                 # entry point
internal/
  cli/                             # cobra subcommands + per-command helpers
    root.go                        # global flags, Execute()
    list.go new.go add.go eject.go # the ten commands
    go_cmd.go rm.go clean.go alias.go config_cmd.go completion.go
    emit.go                        # fd<N> / bare-mode path emission
    picker.go                      # adapts internal/picker to worktrees
    worktrees.go                   # findByBranch, filterOutCurrent, ...
    config_resolve.go              # layer git-config wt.* under CLI flags
    completion_helpers.go          # ValidArgsFunction candidates for go/rm/add
  picker/                          # bubbletea SelectOne / SelectMany / Confirm
  wt/                              # worktree types, parsing, validation
  config/                          # wt.* key registry + git-config IO + ${...} expansion
  copyspec/                        # .git-wt-copy-files parser
  ui/                              # lipgloss styles + duration formatting
  fd/                              # wrapper-protocol fd open/check
  git/                             # thin git subprocess wrapper
  version/                         # build-time version string
e2e/                               # Go-native E2E (drives the built binary)

Each command's RunE is small: it gathers inputs (repo, worktree list, flags) and delegates to focused helpers. The pure helpers live alongside the command file, are unit-tested directly, and don't depend on Cobra or the picker.

Wrapper protocol (fd)

The shell function generated by git-wt alias gwt opens fd N (default 3) via N>&1 1>&2 and captures whatever the binary writes there. The binary:

  • Wrapper mode (fd.Open(N) succeeds): writes the destination path to fd N.
  • Bare mode (no fd N): writes the path to stdout with a → cd '...' hint on stderr. Supports cd "$(git-wt go branch)".

The fd number is baked literally into the redirect at alias-generation time because bash parses redirect operators before variable expansion.

Commands at a glance

Command Description
new <branch> Create worktree at <repo>/.worktrees/<branch>/ (or --parent-dir). Copies files matching .git-wt-copy-files (or built-in defaults). Hints if <parent>/ isn't gitignored when it lives inside the repo.
add [<leaf>] <branch|remote-ref> Worktree for an existing branch. Resolver: <remote>/<rest> (with matching remote ref) ⇒ remote DWIM via --track -b <rest>; else local branch lookup. Optional <leaf> overrides the leaf directory name (defaults to the local branch name). Never creates new branches.
eject [<leaf>] Move HEAD's branch out of the main working tree into a new worktree. Stashes (incl. untracked), switches main tree to main/master/--base, runs the add pipeline, then stash apply --index in the new worktree. Interactive confirm by default. Must run from the main tree, must be on a branch, current branch must not be the base.
rm [branch...] Remove worktree(s). Interactive multi-select if no args. Confirm step is also the type-of-rm choice (worktree only / worktree+branch / cancel). Bounces to main if you remove your current worktree. Fast path (rm_remove.go): clean-check → rename aside → worktree prune → parallel permission-fixing delete (wt.DeleteTree) with a TTY progress line; falls back to git worktree remove for locked/edge cases. Args also match unregistered leftover dirs under the trees dir (rescue).
go [branch] Navigate. Direct branch lookup with unique-suffix fallback (authpaul/auth). Interactive picker if no arg.
list (ls) Print the table. Columns: marker, branch, location (#name inside .worktrees/, rel-to-repo elsewhere, abs outside), mtime.
clean Remove worktrees whose local branch is gone OR whose upstream is gone. Both run by default; narrow with --orphaned-only / --upstream-gone-only.
alias <name> Print a POSIX shell function. Configurable fd (--fd N, range 3-9), baked flags (--plain, -n, --debug), and an optional tab-completion binding (default on; suppress with --no-completion).
completion <shell> Print a Cobra-generated completion script for bash/zsh/fish/powershell. Dynamic argument completion via ValidArgsFunction on go / rm / add.
config [<key> [<value>]] Show/set persistent settings stored in git config wt.*. List bare; show one key with template + resolved value; set with <key> <value> (default --local, --global flag for user-wide); --unset to remove. Validates type and template vars at set time.

Global flags

-h --help, -v --version, --debug, --plain (also honours NO_COLOR), -n --non-interactive (auto-detected when stdin isn't a TTY), --fd <N>.

--plain and --fd layer over their wt.* config defaults — when the user doesn't pass the flag (cmd.Flags().Changed), PersistentPreRun reads the corresponding key. --parent-dir is checked the same way in cli.resolveParentDir. Templating (${repo}, ${repoPath}, ${repoParent}, ${home}) is applied by wt.ResolveParentDir whenever the value is non-empty, so values from CLI or config can both use it.

Build / test

go build -o git-wt ./cmd/git-wt
go test -race -count=1 ./...        # ~95 unit + 17 e2e
go vet ./...

E2E tests run inside t.TempDir() (under /tmp/), never the project repo. CI mirrors this on Linux + macOS.

Conventions

  • Branches that touch flags should make them explicit parameters of the helper they configure (e.g. chooseRmAction(targets, keepBranch, deleteBranch)). Avoid reading package-level flag vars from inside helpers — it makes them un-unit-testable.
  • Pure functions get unit tests in the same package; commands get E2E coverage in e2e/.
  • The picker package is generic — SelectOne / SelectMany / Confirm take row/option slices and return (value, ok, err). ok=false means the user cancelled (ESC, Ctrl-C, q); the caller should exit silently.

Branches and history

  • main — the Go implementation. The Go rewrite (formerly on a migrate-to-go branch) landed here in 2026; branch from main for all new work.
  • zig-cli — frozen snapshot of the pre-migration Zig codebase, retained for reference.
  • The migration spec lives at .ai-cache/plan-go-migration.md.