Raw capture — raw/git-wt.md
url: https://gabri.me/blog/git-wt date_fetched: 2026-07-05 backfilled: true
In my previous post, I explained why the bare repo pattern makes git worktrees actually pleasant to use. But setting it up manually requires 10+ commands. And the day-to-day friction - upstream tracking, orphaned branches, missing fetches - adds up.
So I built git-wt to handle the tedious parts.
How custom git commands work:Any executable namedgit-<cmd>in your$PATHbecomesgit cmd. Git’s own commands work this way - runls $(git --exec-path)to see them all.
Even with the bare repo pattern, native git worktree has friction:
No upstream tracking. Create a worktree from origin/feature, your first push fails with “no upstream configured.” Every single time.
Orphaned branches. Remove a worktree, the local branch stays. Six months later, git branch scrolls for pages.
Manual fetching. Forget to fetch before creating a worktree? You’re working with stale refs.
Setup dance. Want the bare repo structure? Memorize 10 commands or dig through notes.
| Command | What it does |
|---|---|
clone |
Sets up bare structure automatically |
migrate |
Converts existing repos (preserves all your work) |
add |
Creates worktrees with auto-fetch and upstream |
remove |
Removes worktree AND cleans up the local branch |
destroy |
Nuclear: worktree + local branch + remote branch |
update |
Fetches everything, fast-forwards default branch |
switch |
Interactive picker (fzf) to jump between worktrees |
Everything else passes through to git worktree - list, prune, lock all work.
git wt clone https://github.com/user/repo.gitThat’s it. Three things in your folder: .bare/, .git, and a main/
worktree. Compare to the 10 manual commands from the previous post.
The result:
my-project/
├── .bare/ # all git data lives here
├── .git # pointer file
└── main/ # worktree: main branch
git wt addFetches first. Shows branches in fzf with a commit preview pane. Pick the branch, worktree created, upstream configured. Done.
git wt add feature-auth origin/feature-auth # existing branch
git wt add -b my-feature my-feature # new branch
Both modes auto-configure upstream tracking. Your first push just works.
git wt remove feature-paymentsRemoves the worktree directory AND deletes the local branch. No more orphans.
git wt destroy old-experimentRemoves worktree, local branch, AND remote branch. Shows you exactly what will be deleted. Requires typing the branch name to confirm - no accidental destruction.
⚠️
Experimental: This command restructures your repository. It has worked on repos I’ve tested, but as a destructive action, proceed with caution until more users have validated it.
Already have a repo cloned the normal way?
cd my-repo
git wt migrate
Converts your existing clone to the bare structure. Preserves all your branches, stashes, and history. Your current working directory becomes a worktree.
git wt updateFetches all remotes, fast-forwards the default branch (main/master) if possible. Run this periodically to keep your worktrees current.
git wt switchInteractive fzf picker showing all worktrees. Select one and it prints the path. Pair with cd $(git wt switch) or bind to a shell function.
you can add this to your .bashrc or .zshrc:
# Jump to a worktree
wt() {
local dir
dir=$(git wt switch)
[[ -n "$dir" ]] && cd "$dir"
}
Now wt gives you an interactive picker that drops you into the selected worktree.
brew tap ahmedelgabri/git-wt
brew install git-wt
nix run github:ahmedelgabri/git-wtOr add to your flake:
{
inputs.git-wt.url = "github:ahmedelgabri/git-wt";
}
Then use inputs.git-wt.packages.${system}.default.
Copy the git-wt script somewhere in your $PATH. It’s a single bash file with no dependencies beyond git and standard unix tools (fzf for interactive mode).
Includes completions for zsh, bash, and fish. (installed automatically with Homebrew and Nix)
The bare repo pattern is powerful but has sharp edges. git-wt smooths them:
cloneand- migratehandle all the configaddsets it up automaticallyremoveand- destroyprevent orphansThe goal is making worktrees feel as natural as git checkout - but with full isolation.
# New project
git wt clone https://github.com/some/repo.git
cd repo/main
# Existing project
cd existing-repo
git wt migrate
Work like this for a week. The friction disappears.
Links: