wt wraps git worktree in a shorter command set. It keeps every worktree for a
repository under one directory, moves your shell into them, and reports the state
of all of them at once. Works in zsh and bash.
git worktree handles the mechanics but leaves you to invent a path for each
checkout and to cd there yourself. wt decides the path and moves the shell.
- git 2.17 or later.
wt rmrelies ongit worktree remove, added in that release. - git 2.36 or later for the
[locked]andprunableindicators inwt ls. Older git omits those fields fromgit worktree list --porcelain, so those worktrees just showok. - zsh or bash.
antigen bundle 1henrypage/sh-treehouse --branch=mainzinit light 1henrypage/sh-treehouseor
zplug "1henrypage/sh-treehouse"Clone the repo and source the plugin file in your .zshrc, before compinit:
source /path/to/sh-treehouse/sh-treehouse.plugin.zsh
autoload -Uz compinit && compinitSourcing first puts the completions directory on fpath in time for compinit
to find it. It also means the plugin's own compdef _wt wt line runs before
compdef exists, so you'll see one harmless compdef: command not found when the
shell starts. Completion still works: completions/_wt starts with #compdef wt,
so compinit registers it directly when it autoloads the file.
Add to your .bashrc:
export PATH="/path/to/sh-treehouse/bin:$PATH"
eval "$(wt init bash)"Bash gets the wt shell function that handles cd for add, checkout, and
base. There's no bash completion; completions/_wt is zsh-only.
wt add hotfix/payment-bugCreates a worktree for the branch and moves you into it.
wt add other-branch
wt lsLists every worktree for the repo, with its status.
wt run hotfix/payment-bug 'npm test'Runs a command inside a worktree without moving you there.
wt checkout other-branch
wt baseMoves between worktrees, and back to the main repo.
wt rm -y hotfix/payment-bugRemoves the worktree and deletes the branch. -y skips the confirmation and
always deletes - use it once you're sure.
| Command | Description |
|---|---|
wt add <branch> |
Create a worktree for a branch and move into it |
wt rm [-f] [-y] <branch> |
Remove a worktree; -y also deletes the branch |
wt ls |
List all worktrees with their status |
wt checkout <branch> |
Move into an existing worktree |
wt base |
Move back to the main repo |
wt prune |
Clean up stale worktree references |
wt status |
Show git status across all worktrees |
wt lock <branch> |
Lock a worktree to prevent removal |
wt unlock <branch> |
Unlock a locked worktree |
wt run <branch> <cmd> |
Run a command inside a worktree |
| Command | Description |
|---|---|
wt init <shell> |
Print shell integration code for eval (zsh or bash) |
wt help |
Show help |
wt add resolves the branch in three steps: an existing local branch is
checked out as-is, a remote-only branch becomes a local branch tracking it, and
anything else becomes a new branch off HEAD. It doesn't fetch first, so a branch
that only exists on the remote and hasn't been fetched yet won't be found.
wt ls shows a status for each worktree: ok for clean, * for uncommitted
changes, prunable for a worktree whose directory is gone, and [locked]
alongside any of those for a locked worktree.
wt rm prompts before deleting the branch, and reads that prompt from
/dev/tty rather than stdin, so piping y into it does nothing. It also only
accepts a single y or Y. -y answers yes without a prompt, which means it
deletes the branch - the scriptable path, but a destructive one, so use it
deliberately.
wt run passes your command to eval, not exec, so it's re-parsed by the
shell. Quote it as one argument if it has spaces or shell operators:
wt run my-branch 'npm test && npm run build'.
| Variable | Default | Description |
|---|---|---|
WT_DIR |
~/.treehouse |
Base directory where worktrees are stored |
NO_COLOR |
unset | Set to any value to disable coloured output |
wt reads WT_DIR fresh on every invocation, so you can set it in your rc file,
export it for a single shell session, or override it per command:
WT_DIR=/tmp/worktrees wt add experimentWorktrees live under $WT_DIR/<repo-name>/<branch>. The repo name comes from the
origin remote's URL; if there's no origin, it falls back to the basename of
the main repo's root directory.
Branch names with slashes are flattened with --:
| Branch | Directory |
|---|---|
main |
~/.treehouse/myrepo/main |
feature/login |
~/.treehouse/myrepo/feature--login |
fix/auth/token |
~/.treehouse/myrepo/fix--auth--token |
wt checkout, wt add, and wt base move your shell by emitting a marker that
the wt() shell function, installed by wt init, catches and turns into a cd.
If you run wt directly without that function on your PATH - for example by
calling command wt, or by skipping wt init entirely - these commands print
Run: cd <path> instead of moving you there.
Zsh gets completion for every subcommand, including wt init <TAB> (offers zsh
and bash) and wt rm -<TAB> (offers -f/--force and -y/--yes). Press
<TAB> after any subcommand:
wt <TAB>- every subcommandwt add <TAB>- branches available to add, excluding ones already checked outwt checkout <TAB>- branches with an existing worktreewt lock <TAB>- worktrees that aren't lockedwt unlock <TAB>- worktrees that are locked
Bash has no completion; completions/_wt is zsh-only.
bash tests/run_all.shRuns the full test suite and prints TAP output. CI runs the same command on Ubuntu and macOS for every pull request.
MIT
