A tiny wt helper for git worktrees
git worktree is powerful—and a little clunky. I use it constantly, so I wrapped the day‑to‑day
into a tiny shell function named wt. Think of it as a small command palette for worktrees:
list what exists, create new ones from local or remote branches, seed a fresh branch from a base,
prune dangling directories, and tear things down when I’m done.
Here’s the function:
wt () {
if [[ "$1" == "help" || "$1" == "--help" || "$1" == "" ]]
then
cat <<'EOF'
Usage: wt <command> [options]
Commands:
list|ls Show active worktrees
add|new [-b base] [path] Add a worktree for <branch>; defaults path to ../<repo>-<branch>
remove|rm <path|branch> Remove a worktree by path or branch name
prune Run git worktree prune
Examples:
wt list
wt add feature/login
wt add -b main feature/login ../product-site-login
wt remove feature/login
EOF
return 0
fi
local subcommand=$1
shift
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1
then
echo "wt: run inside a git repository" >&2
return 1
fi
local repo_root
repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || return 1
local repo_name
repo_name=$(basename "$repo_root")
case "$subcommand" in
(list | ls) git worktree list "$@" ;;
(add | new) local branch=""
local target_path=""
local base_branch=""
while [[ $# -gt 0 ]]
do
case "$1" in
(-b | --base) base_branch=$2
shift 2 ;;
(-p | --path) target_path=$2
shift 2 ;;
(--help | -h) wt help
return 0 ;;
(*) if [[ -z "$branch" ]]
then
branch=$1
elif [[ -z "$target_path" ]]
then
target_path=$1
else
echo "wt add: unexpected argument '$1'" >&2
return 1
fi
shift
continue ;;
esac
done
if [[ -z "$branch" ]]
then
echo "wt add: branch name required" >&2
return 1
fi
local sanitized_branch=${branch//\//-}
if [[ -z "$target_path" ]]
then
target_path="$(dirname "$repo_root")/${repo_name}-${sanitized_branch}"
fi
if git show-ref --verify --quiet "refs/heads/$branch"
then
git worktree add "$target_path" "$branch"
return $?
fi
if git show-ref --verify --quiet "refs/remotes/origin/$branch"
then
git worktree add --track -b "$branch" "$target_path" "origin/$branch"
return $?
fi
if [[ -n "$base_branch" ]]
then
git worktree add -b "$branch" "$target_path" "$base_branch"
return $?
fi
echo "wt add: branch '$branch' not found. Use --base <existing-branch> to create it." >&2
return 1 ;;
(remove | rm) if [[ $# -eq 0 ]]
then
echo "wt remove: provide a path or branch name" >&2
return 1
fi
local input=$1
local target=""
if [[ -d "$input" ]]
then
target=$input
else
target=$(git worktree list --porcelain | awk -v branch="$input" '
/^worktree / {w=$2}
/^branch / {
b=$2
sub(/^refs\/heads\//,"",b)
if (b == branch) {
print w
exit
}
}
')
if [[ -z "$target" ]]
then
local guess="$(dirname "$repo_root")/${repo_name}-${input//\//-}"
if [[ -d "$guess" ]]
then
target=$guess
fi
fi
fi
if [[ -z "$target" ]]
then
echo "wt remove: unable to resolve worktree for '$input'" >&2
return 1
fi
git worktree remove "$target" ;;
(prune) git worktree prune "$@" ;;
(*) echo "wt: unknown command '$subcommand'. See 'wt help'." >&2
return 1 ;;
esac
}
Drop it into your shell config and tweak to taste. Now you’ve got a friendly front end for a very capable Git feature.
I use wt all day—especially as more of my workflow involves AI. I want git to be
something I don’t think about. This little wrapper helps by:
- Detecting whether a branch exists locally or on
originand choosing the rightgit worktree addflags. - Creating a predictable directory name (
../<repo>-<branch>) when I don’t care where the worktree lives. - Seeding a brand‑new branch off a base branch with
wt add -b main feature/login. - Resolving worktrees by branch name or path when removing, so
wt rm feature/loginjust works.
A quick workflow
# Move into a repo (this site, for example)
cd ~/git/website
# Start a branch from main and keep the directory name tidy
wt add -b main worktree-post ../web-worktree-post
# Switch into that worktree (often in a second terminal tab)
cd ../web-worktree-post
# Clean out a worktree once the branch is merged
wt rm worktree-post
# Occasionally clear dangling entries
wt prune
It removes just enough friction that I actually use worktrees during day‑to‑day work—
especially when juggling multiple fixes or pairing with an AI agent. For more on why
worktrees pair nicely with AI‑driven development, see
AI is better with git worktrees.
Stacked PRs without extra tooling
Stacked pull requests keep several branches alive at once—each built on top of the previous layer. Graphite popularized the idea for me, but it leans on a single working tree. If you live in worktrees, you can approximate the workflow yourself.
With wt, it stays low‑effort: spin up wt add -b main feature/base, build the next
layer with wt add feature/base feature/forms, and you get separate directories where
each patch stays focused. When it’s time to tidy up, wt rm feature/forms and friends
make cleanup just as quick.
Another way to think about it: each worktree is a parallel workstream. If I’m building a new API endpoint, I might do:
cd ~/git/website
wt add -b main api ../web-api
cd ../web-api
From here you can keep it simple—just start checking out branches in the worktree. For stacked PRs, we want child branches. That sounds tedious without Graphite, but there’s a very handy Git trick…
Tip: make a child branch in place
After -b, git checkout accepts a start‑point. Passing the parent branch creates the
child at the right base:
# ~/git/website — currently on branch `api`
git add -A
git commit -m "<insert message>" && git push
# Create a new branch `authorization` with `api` as the parent
git checkout -b authorization api
# Continue working - now on branch `authorization`
That’s it: worktrees with stackable PRs. You can keep multiple patch stacks moving in parallel—human and AI included. Happy shipping.