commands
Eight subcommands. Worktrees in, listed, switched, and pruned; a diff explained; a line traced back; a shell wrapper printed.
| command | what it does |
|---|---|
wh new <branch> [--from <ref>] | create a worktree in a sibling dir, copy .env* files into it |
wh ls | list worktrees with dirty count and ahead/behind |
wh switch [query] | pick a worktree (or match one) and cd into it |
wh rm [name] [--dry-run] [--yes] [--force] | remove merged worktrees, or one by name |
wh explain [range] [--uncommitted] [--changelog] [--describe] [--chat] [--dry-run] [-- <pathspec>] | a plain-english review, release notes, or a pr draft for a diff |
wh why <path>:<line> [--chat] [--dry-run] | why a line exists: git blame, then the commit that last touched it |
wh models | the models the provider offers, asked of the provider itself |
wh init <zsh|bash|fish> | print the shell wrapper that makes switch a real cd |
--help on any of them is short and lowercase. Colors only when stdout is a terminal and NO_COLOR is unset.
Exit codes:
0success, including a declined prompt1any error, or a cancelled picker2bad usage: a missing argument,--forcewithout a name,--changelogtogether with--describe,--uncommittedtogether with a range,--chattogether with--dry-run
wh new
- The worktree lands next to the main one as
<repo>.<branch>, anchored to the main worktree, never to where you stand /and other unsafe characters become-;.and_stay; if nothing survives:cannot derive a directory name from '<branch>'- The branch is created from
HEADwhen missing;--from <ref>branches from elsewhere and is ignored when the branch exists - Top-level
.envand.env.*files are copied from the worktree you stand in (not.envrc, nothing recursive), skipping any that exist; nocopiedline when there was nothing to copy - Refuses with a one-line reason when the directory exists or the branch is checked out elsewhere
wh ls
- main worktree first, then alphabetical
- Status:
clean,N dirty, orstale(a worktree git can no longer read, or one it would prune) - The muted column:
ahead N,behind N, or both, against the upstream - A detached worktree shows as
<sha> detached
wh switch
- Type to filter, arrows or ctrl-p / ctrl-n to move, enter to select, esc or ctrl-c to cancel
- An exact name wins over a substring match; an ambiguous query lists the matches and fails with
'<query>' matches N worktrees - The picker draws on
/dev/ttyand prints only the chosen path to stdout, which thewh initwrapper turns into acd; without the wrapper,cd "$(wh switch)"does the same - With no terminal and no query:
not a terminal; pass a query: wh switch <query>
wh rm
With no name, wh rm prunes worktrees whose branches are merged into the default branch and deletes those branches. Merged means one of:
- The branch is an ancestor of the default branch
- Every one of its commits has an equivalent patch there (a rebase merge)
- Its whole tree landed as a single commit (a squash merge)
It never touches dirty, locked, or detached worktrees, the main worktree, or the one you stand in. Dirty candidates are listed as skipped <path> (<branch>): N dirty; with nothing to do it says nothing to prune. A prune ends with pruned N worktree(s); a named removal prints removed <path> (<branch>) and no summary line.
--force
--force removes a dirty or unmerged worktree and needs a name. Without it, a named removal refuses with <path> is dirty (use --force), <branch> is not merged (use --force), or detached worktree (use --force). The main and the current worktree are always refused, and so is a locked one.
wh why
git blame says who and when. This says why.
- The line is blamed locally, so this needs no network beyond the model call
- The commit that last touched it becomes the payload, cut to that file; the answer is a
whysection thenwatch out - The status line names the blaming commit (
reading src/git.rs:42 · last changed in 68d394f by Ada on 2026-09-02), and a span adds how many other commits touch it - A line you have not committed yet says so instead of guessing; a line past the end of the file gets git's own words
--dry-runprints the payload; the line itself is appended after it, never inside, so the payload stays byte for byte what the spec describes
wh models
Asks the provider, with the key wh explain already uses, so no list shipped in the binary has to be kept current. Embedding, speech, and image models are left out. Nothing is cached: this is a live call.
pinned defaults
The default model per provider never follows "latest": a default that moves changes cost and behaviour without asking. When the provider no longer lists the one in use, wh models says so and changes nothing, and WH_MODEL picks another. A base url with no models endpoint (a gateway that only proxies chat) says provider has no models endpoint rather than pretending the model is missing.
wh init
wh init zsh (or bash, fish) prints a small wh() function that forwards every command and turns wh switch into a cd. Nothing is written; you eval it from your rc file. See install.
related
- wh explain: ranges, uncommitted work, modes, follow-ups
- Configuration: keys, providers, gateways