Where to put git worktrees when you have many repos
Comparing three git worktree layouts for many local repos, and why I settled on ~/worktrees/<repo>/<slug>.
I use git worktrees to work on multiple branches of a repo in parallel, and across a dozen-plus repos the question of where those worktree directories live started to matter.
The Problem
I had worktrees scattered as siblings of their checkouts, all sharing one org-mirrored parent directory (github.com/<org>/ and gitlab.com/<org>/). Three things broke.
- Collisions and litter. A worktree named
feat-audit-gatefrom one repo sat next tofeat-audit-gatefrom another, and the parent dir accumulated stray entries likefeat/that no longer belonged to any repo. - Ambiguity. With worktrees named by branch slug and repos named by project, a directory like
foo-chartscould be a repo or a worktree offoo. That is exactly the confusion sellout’s worktree gist calls out. - No single place to manage them. Listing, pruning, and backing up worktrees meant scanning a shared directory mixed with real checkouts.
I wanted a layout chosen on merit for many repos, not one inherited from how I happened to start.
What Changed
I compared three layouts on structural criteria (working-tree isolation, collision-free scaling, clean-by-default tooling, and backup scoping) rather than on habit.
| Layout | Working-tree isolation | Scales across repos | Clean by default |
|---|---|---|---|
~/worktrees/<repo>/<slug> (centralized) | fully outside | repo-namespaced | yes |
../<repo>/<slug> (sibling of checkout) | outside | shared parent pollutes | mixed into project tree |
../<repo>/.worktrees/<slug> (nested, hidden) | inside the checkout | per-checkout | noisy unless configured |
I went with the centralized one: ~/worktrees/<repo>/<type>-<slug>. The root defaults to ~/worktrees and follows $WORKTREES if set; the per-worktree directory flattens the branch’s slash (feat/new becomes feat-new).
Two facts decided it. First, git allows a worktree inside another worktree’s working tree, but it then shows as untracked in git status and needs a .gitignore entry. commandcode.ai’s worktree guide puts the recommendation plainly: keep worktrees outside the repo so they never nest, never appear in the file tree, and never need ignoring. Second, standard tooling (grep -r, find, editor indexers, fuzzy finders) descends into nested directories by default; a nested worktree means opting out of noise everywhere, while a centralized ~/worktrees means opting in to a scope only when you want it.
The canonical checkouts stay exactly where they were: ~/Documents/Projects/github.com/<org>/<repo> and ~/work/gitlab.com/<org>/<repo>. Only the worktrees move.
Results
Git links a worktree to its repo by metadata, not by path proximity, so the worktree can live anywhere on disk. I verified the three things that mattered for the switch:
# A worktree at an absolute path far from the repo just works
git worktree add -b feat/new ~/worktrees/myrepo/new-style
# `git worktree list` shows mixed locations: old siblings and new ones under ~/worktrees coexist
git worktree list
# Relocate an existing sibling worktree under ~/worktrees without rebuilding it
git worktree move ../myrepo-old-style ~/worktrees/myrepo/old-style
The practical upshot: I didn’t have to migrate anything. Existing sibling worktrees keep working where they are, registered in the same repo’s .git/worktrees/. New ones go to ~/worktrees/<repo>/. The list shows both, and my editor’s worktree switcher, which enumerates git worktree list --porcelain and jumps to the absolute path, handles them identically. When an old worktree’s branch is done, the next one is recreated under ~/worktrees/<repo>/, or git worktree move relocates it in place.
The one gotcha I hit: my switcher labeled entries as ../<basename>, which is accurate for sibling worktrees but wrong for ~/worktrees/... ones. A one-line fix to abbreviate the path relative to home (vim.fn.fnamemodify(path, ":~")) resolved it for both.
There’s a stronger pattern I haven’t adopted: colocation, which joncardasis’s gist calls the “gold standard”. Instead of a normal clone plus separate worktrees, you keep one bare repository (a git database with no working tree of its own) and add every branch, including main, as a linked worktree beside it. Because nothing is checked out at the repository root, no branch is privileged: the main worktree can be removed as freely as any other, which also sidesteps the rule that a branch checked out in one worktree is locked there. It’s an upgrade I can move to later without changing the ~/worktrees/<repo>/ path.
References
- git-worktree documentation:
worktree add,worktree move,worktree list --porcelain. - Git worktree layout, sellout’s gist: the sibling-naming ambiguity (
foovsfoo-charts) that motivated a project container. - Organizing Git Worktrees with Colocation, joncardasis’s gist: the “colocation” pattern, a bare repository with linked worktrees.
- Git Worktrees, commandcode.ai: the case for keeping worktrees outside the working tree.
This post was written with AI assistance.