5 minutes
Git Worktrees: Work on Two Branches at Once
Meet git worktree
You’re mid-feature, working tree is dirty, half-finished file sitting there uncommitted. Then a bug report lands against main. The usual way to deal with this:
git stash
git checkout main
# fix the bug
git checkout feature/whatever
git stash pop
It works, but it’s a dance, and it gets worse the more files you’ve touched. git worktree skips the dance entirely: instead of switching branches inside one working directory, you get a second working directory, backed by the same repository, checked out to a different branch. Your dirty feature branch stays exactly as you left it while you fix the bug somewhere else.
How it works
git worktree add <path> <branch>— creates a new working directory at<path>, checked out to<branch>git worktree add -b <new-branch> <path> <start-point>— create the branch and the worktree in one shotgit worktree list— show every worktree linked to the repogit worktree remove <path>— remove a worktree once you’re done with itgit worktree prune— clean up leftover metadata if you deleted a worktree directory by hand instead of usingremove
One constraint worth knowing up front: a branch can only be checked out in one worktree at a time. Git will refuse if you try to check it out twice, more on that at the end.
Example
Let’s build a small Python project to make this concrete. All you need is python3, no extra dependencies, tests use the built-in unittest module.
mkdir worktrees-demo && cd worktrees-demo
git init -b main
Add the “current” implementation and its tests:
# discount.py
def discount(price, percent):
"""Return price after applying a percentage discount."""
return price - (price * percent / 100)
# test_discount.py
import unittest
from discount import discount
class TestDiscount(unittest.TestCase):
def test_ten_percent(self):
self.assertEqual(discount(100, 10), 90)
def test_zero_percent(self):
self.assertEqual(discount(50, 0), 50)
if __name__ == "__main__":
unittest.main()
echo "__pycache__/" > .gitignore
git add .
git commit -m "Add discount function with tests"
Now start a feature branch and leave some work in progress, uncommitted, on purpose:
git checkout -b feature/bulk-discount
# bulk.py -- work in progress: bulk order discount tiers
def bulk_discount(order_total):
pass # TODO
git status --short
?? bulk.py
Dirty working tree, unfinished feature. This is the moment a bug report shows up: discount() should never push a price below zero, but discount(100, 150) currently returns -50.0. You don’t want to touch this working tree to fix it. Create a worktree for main instead:
git worktree add ../worktrees-demo-hotfix main
Preparing worktree (checking out 'main')
HEAD is now at 2decc2b Add discount function with tests
git worktree list
/path/to/worktrees-demo 2decc2b [feature/bulk-discount]
/path/to/worktrees-demo-hotfix 2decc2b [main]
Two working directories, one repository, two different branches checked out simultaneously. cd into the new one and reproduce the bug with a failing test first:
cd ../worktrees-demo-hotfix
# test_discount.py -- add this test to the class
def test_never_goes_negative(self):
self.assertEqual(discount(100, 150), 0)
python3 -m unittest -v
test_never_goes_negative (test_discount.TestDiscount.test_never_goes_negative) ... FAIL
test_ten_percent (test_discount.TestDiscount.test_ten_percent) ... ok
test_zero_percent (test_discount.TestDiscount.test_zero_percent) ... ok
======================================================================
FAIL: test_never_goes_negative (test_discount.TestDiscount.test_never_goes_negative)
AssertionError: -50.0 != 0
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (failures=1)
Bug reproduced. Fix it:
# discount.py
def discount(price, percent):
"""Return price after applying a percentage discount, never below 0."""
return max(price - (price * percent / 100), 0)
python3 -m unittest -v
test_never_goes_negative (test_discount.TestDiscount.test_never_goes_negative) ... ok
test_ten_percent (test_discount.TestDiscount.test_ten_percent) ... ok
test_zero_percent (test_discount.TestDiscount.test_zero_percent) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Green. Commit the fix, right on main:
git add -A
git commit -m "Fix: discount should never go below 0"
git log --oneline
2e4fc1c Fix: discount should never go below 0
2decc2b Add discount function with tests
Now go back to where the feature work was happening:
cd ../worktrees-demo
git status --short
git log --oneline
?? bulk.py
2decc2b Add discount function with tests
bulk.py is still sitting there untouched, uncommitted, exactly as it was. No stash, no pop, no risk of forgetting to bring it back. The hotfix commit lives on main in the other worktree and hasn’t touched this branch at all — you’d merge or rebase onto the updated main when you’re ready to pick it up.
Once the hotfix worktree isn’t needed anymore, remove it:
git worktree remove ../worktrees-demo-hotfix
git worktree list
/path/to/worktrees-demo 2decc2b [feature/bulk-discount]
Back to a single worktree.
The one-branch-one-worktree rule
Try to check out a branch that’s already checked out somewhere else, and Git stops you:
git worktree add ../worktrees-demo-again feature/bulk-discount
Preparing worktree (checking out 'feature/bulk-discount')
fatal: 'feature/bulk-discount' is already used by worktree at '/path/to/worktrees-demo'
Makes sense: two working directories editing the same branch at once would make the index and HEAD ambiguous. If you need to poke at the same branch from two places, checkout a new branch from it instead.
Bonus tips
- Run an old branch’s test suite in a worktree side by side while you keep developing, handy for quick regression comparisons without losing your place
- Stashes, branches, tags, and the reflog are shared across all worktrees of a repo, only the working tree contents and the index are per-worktree
- Keep worktrees as sibling directories (
../repo-hotfix) rather than nested inside the repo, nesting them inside confuses.gitignoreand most tooling
Conclusion
git worktree turns “I need to be on two branches at once” from a stash-and-pray routine into a cd. For interruptions, hotfixes, or just comparing two versions of the same codebase side by side, it’s one of those git features that’s easy to forget exists and immediately useful once you remember it.