Skip to main content
Settings
Search
Appearance
Theme Mode
About
Jekyll v3.10.0
Environment Production
Last Build
2026-07-02 02:34 UTC
Current Environment Production
Build Time Jul 02, 02:34
Jekyll v3.10.0
Build env (JEKYLL_ENV) production
Quick Links
Page Location
Page Info
Layout article
Collection hacks
Path _hacks/git-worktree-two-branches-at-once.md
URL /hacks/git-worktree-two-branches-at-once/
Date 2026-07-01
Theme Skin
SVG Backgrounds
Layer Opacity
0.6
0.04
0.08

Stop stashing to switch branches: check out two at once with git worktree

You’re three edits deep into a feature, nothing compiles, and someone drops “can you hotfix prod real quick.” The reflex is muscle memory: git stash, git switch main, fix it, switch back, git stash pop, and then spend ten minutes remembering what half-finished thought you stashed.

git worktree deletes that whole dance. It checks out a second branch into a second directory that shares the same repo. Your half-done work sits exactly where you left it — a different folder, a clean tree, same .git. No stash. No context to rebuild.

Every command below was run for real. Both ways it bites you stay in, because both are going to happen to you.

Set up a repo you’re mid-change on

One commit, then a deliberately dirty tree — you’re partway through something:

$ git init -q -b main project && cd project
$ echo "print('hi')" > app.py
$ git add app.py && git commit -q -m "initial commit"
$ echo "print('half-done work')" >> app.py   # you, mid-thought
$ git status --short
 M app.py

That ` M app.py` is your uncommitted work. The old way, you’d have to hide it before you could touch another branch. Watch what worktree does instead.

Add a second working tree, no stash

git worktree add <path> -b <newbranch> <start-point> creates a new directory with a fresh branch checked out from where you point it:

$ git worktree add ../hotfix -b hotfix main
Preparing worktree (new branch 'hotfix')
HEAD is now at c211bbb initial commit

Now list what you’ve got checked out:

$ git worktree list
/tmp/demo/project c211bbb [main]
/tmp/demo/hotfix  c211bbb [hotfix]

Two branches, checked out at the same time, in two folders. cd ../hotfix, fix prod, commit, push. Your feature work never moved:

$ cat app.py            # in project/ — still dirty, untouched
print('hi')
print('half-done work')
$ cat ../hotfix/app.py  # in hotfix/ — clean checkout of main
print('hi')

You’ll know it worked when git worktree list shows two paths, the hotfix tree is clean, and your half-done line is still sitting in the original folder. No stash entry to remember.

One detail that surprises people: the .git in a worktree is a file, not a directory. It’s a pointer back to the real repo:

$ cat ../hotfix/.git
gitdir: /tmp/demo/project/.git/worktrees/hotfix

That’s why the history, remotes, and config are shared — there’s exactly one .git database, and every worktree links to it.

The part where it breaks: one branch, one tree

The first thing everyone tries is checking out main a second time so they have a “clean copy.” Git refuses:

$ git worktree add ../another main
Preparing worktree (checking out 'main')
fatal: 'main' is already used by worktree at '/tmp/demo/project'

This is a feature, not a bug. If two directories had main checked out, a commit in one would leave the other’s index and working tree lying about what HEAD is — a great way to “lose” a commit. So git enforces one branch, one working tree. If you genuinely want a second copy of the same branch’s contents, check out a new branch that starts there (-b review main) or use a detached checkout (git worktree add --detach ../peek main), which isn’t “on” any branch and so can’t collide.

The other part where it breaks: the stale ghost

Here’s the one that bites weeks later. You finish the hotfix, and you clean up the obvious way — you delete the folder:

$ rm -rf ../hotfix

Feels done. It isn’t. Git still thinks that worktree exists, because you removed the directory but not git’s bookkeeping entry for it:

$ git worktree list
/tmp/demo/project c211bbb [main]
/tmp/demo/hotfix  c211bbb [hotfix] prunable

See that prunable tag — git is telling you it’s a ghost: the branch is still locked to a worktree whose files are gone, so trying to check hotfix out elsewhere still fails. Clear the bookkeeping with prune:

$ git worktree prune -v
Removing worktrees/hotfix: gitdir file points to non-existent location
$ git worktree list
/tmp/demo/project c211bbb [main]

Now it’s actually gone. The lesson: don’t rm -rf a worktree — use git worktree remove <path>, which deletes the directory and the bookkeeping in one step (and refuses if you have uncommitted changes there, which rm -rf would have silently eaten).

The whole safe flow, tested

Here’s the shape to reach for, wired to add a worktree, prove the original tree stays dirty and untouched, and tear it down the clean way. This block is opted into our test harness (lh:run) and runs on every build in a locked-down, no-network sandbox, so the version you’re reading is the version that passed:

```bash lh:run #!/usr/bin/env bash set -euo pipefail

export GIT_AUTHOR_NAME=you GIT_AUTHOR_EMAIL=you@example.com export GIT_COMMITTER_NAME=you GIT_COMMITTER_EMAIL=you@example.com

root=”$(mktemp -d)”; cd “$root” git init -q -b main project && cd project echo “print(‘hi’)” > app.py git add app.py && git commit -q -m “initial commit”

You’re mid-change: leave the tree dirty on purpose.

echo “print(‘half-done work’)” » app.py

echo “==> main tree is dirty:” git status –short

Second working tree for a hotfix branch — no stash, no commit.

git worktree add -q ../hotfix -b hotfix main

echo “==> two worktrees checked out at once:” git worktree list

echo “==> hotfix tree is clean; your half-done work is untouched:” git -C ../hotfix status –short && echo “ (hotfix: nothing to show — clean)” grep -q “half-done” app.py && echo “ (main: your uncommitted line is still here)”

Clean up the RIGHT way — removes the dir AND the bookkeeping.

git worktree remove ../hotfix echo “==> after ‘worktree remove’, back to one:” git worktree list echo “done” ```

All the console output above is real, captured with git version 2.54.0.

When this goes wrong

  • fatal: '<branch>' is already used by worktree at … — you tried to check out a branch that’s live in another tree. That’s the one-branch-one-tree rule. Start a new branch from it (-b) or use --detach.
  • A prunable entry that won’t die — you rm -rf‘d a worktree instead of git worktree remove. Run git worktree prune to clear the ghost, then use remove next time.
  • worktree remove refuses — it won’t delete a tree with uncommitted changes or untracked files, on purpose. Commit them, or force it with --force once you’re sure there’s nothing to lose.
  • Submodules and worktrees are still awkward. If your repo has submodules, each worktree needs its own git submodule update; they aren’t shared like the main history is. Budget a minute for it.

The reflex was stash-switch-fix-switch-pop. The replacement is one git worktree add and a cd. Your unfinished thought stays on disk, in its own folder, exactly where you left it — and the only thing you have to remember is to tear it down with remove, not rm.