git worktree as a daily driver, not a party trick
I used to mock people who used git worktree. It seemed like a weird party trick — “look, I can have two branches checked out at once!” Who needs that? You have one brain, one branch.
Then I spent three months doing a big refactor that required constantly switching between the feature branch and main for comparisons, bug fixes, and production hotfixes. By the end, I was using worktree every day. Three years later, I set up worktrees automatically on any repo I work in, and I can’t go back.
The mental model
git worktree lets you check out different branches into different directories, all sharing the same .git repo under the hood. You get cheap branches-as-directories without the overhead of cloning.
cd ~/code/myproject
git worktree add ../myproject-feature-x feature/x
Now you have two directories. ~/code/myproject is on main. ~/code/myproject-feature-x is on the feature branch. You can cd between them like any other directory. Each has its own index, its own working files, its own state. The .git data (objects, refs, packfiles) lives in the original repo.
Why this changed my workflow
No more stash/checkout dance. When I’m mid-feature and need to fix a prod bug, I don’t stash. I cd ~/code/myproject-main and fix it there. Feature branch state is preserved exactly.
Parallel builds. If I’m trying to bisect a slow bug, I can have git bisect running in one worktree while I continue developing in another. Each build uses the right source tree for its branch.
Running two versions side-by-side. Debugging a regression? Run the old version in one terminal, new in another, hit them with the same curl. Pretty standard scientific method.
Review PRs without disrupting your work. git worktree add ../myproject-pr-review origin/pr-branch — poof, a clean checkout of the PR on disk. Run it, look at it, delete the worktree when done.
The shell setup I actually use
I have an alias gwt that does the common thing:
gwt() {
local branch="$1"
if [ -z "$branch" ]; then
git worktree list
return
fi
local dir="../$(basename $(git rev-parse --show-toplevel))-${branch//\//-}"
if git show-ref --verify --quiet "refs/heads/$branch"; then
git worktree add "$dir" "$branch"
else
git worktree add "$dir" -b "$branch" origin/main
fi
cd "$dir"
}
gwt feature/x creates the worktree, cds into it, and I’m working. No ceremony.
To remove:
git worktree remove ../myproject-feature-x
Or if you’ve already deleted the directory, git worktree prune cleans up the stale references.
The gotchas
Some tools don’t love shared .git layouts. Very occasionally a tool gets confused that .git is actually a pointer file (linked worktrees have a .git file that points to the real git dir, not a .git directory). I’ve hit this with one or two IDE integrations over three years. Usually resolved by updating the tool.
Don’t worktree-checkout the same branch twice. Git doesn’t let you. If you’re in myproject on main and try git worktree add ../somewhere main, it fails. Occasionally confusing if you forget you already have a worktree for that branch somewhere.
Environment leakage. If your project uses direnv or similar, different worktrees share the project path… no, actually, they don’t. Each worktree is its own directory, so direnv treats them separately, which is usually what you want, but means you might need to direnv allow in each. Not a huge deal, but a thing to remember.
node_modules per worktree. Every worktree is a full filesystem copy of the source tree, so you install dependencies in each. For big JS projects this is annoying. pnpm’s content-addressed store helps. For Python, each worktree needs its own venv. This is the biggest real cost.
How I’d explain it to someone new
The alternative to worktree is: “stash everything, checkout the other branch, work there, checkout back, unstash, resume.” Every context switch has overhead. With worktree, a context switch is cd ... That’s it. You have one less thing in your head.
The branch-as-a-folder mental model also matches how I actually think. “Where’s my feature work?” In that folder over there. “Where’s main?” In this folder. The abstraction of “one filesystem location, many logical branches” was always a compromise.
Reflection
The frustrating thing about worktree is that it’s been in git since 2015 and nobody mentioned it to me. I found out about it from a Stack Overflow answer about something unrelated. The git docs have it but don’t highlight it. There isn’t a moment in any tutorial where someone tells you “hey, this is a standard feature and it’s great.”
If you haven’t tried it: set the alias above, use it for a week. If you don’t like it, nothing is lost — worktrees are easy to delete. I bet you stick with it.
Related: Justfile patterns for a crowded monorepo.