~/blog/bare-repo-worktrees cat a-bare-repo-as-the-worktree-root.md

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:

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.

~/blog