A bare repo as the worktree root
I stopped keeping one working copy per repo a while ago. Switching branches
means stashing, or it means a dirty build, or it means I lose my place. Git
worktrees fix that — every branch gets its own directory, sharing one object
store — but the default layout fights you. Clone normally and your .git
lives inside a checkout, so the repo root is also a working tree. It works,
but it’s lopsided.
The cleaner shape: clone bare, and let every checkout be a worktree hanging off it. The root holds the object store and nothing else. Here’s the setup that actually sticks.
Clone
git clone --bare <repo-url>
The line everyone forgets
A bare clone doesn’t set up remote-tracking refs the way a normal clone does,
so git fetch won’t map remote branches into origin/*. Fix the refspec
once:
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
Now fetching and tracking behave like you’d expect:
git fetch origin
git branch --set-upstream-to=origin/main main
Skip this and you’ll wonder why git worktree add for a remote branch can’t
find it. It’s not Git being weird — the bare clone just never wrote the
refspec for you.
A justfile so I never type it again
The mechanical parts — add a worktree, name the directory, base it off main
— are the same every time. I keep them in a shared worktree.just and import
it per repo:
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
base := "main"
import "~/.config/just/worktree.just"
That gives me just wt <branch> to open or create a worktree, just wt-list,
and just wt-rm-pick for an fzf picker that tears one down. The recipes are
thin — they all delegate to one bash script that does the real work: resolving
a base branch, wiring upstream tracking for remote branches automatically, and
refreshing my tmux/tms sessions so a new worktree shows up as a window.
It’s too long to paste here, so the full thing lives in my dotfiles:
worktree.just— the recipesgit-worktree-tms.bash— the helper they call
That’s the whole trick. Clone bare, fix the refspec, let just do the
repetitive bits. The repo root stops pretending to be a checkout, and every
branch finally gets its own clean room.