Web Development · Developer Tooling
Git Worktrees: Check Out Multiple Branches at Once Without the Stash Dance
Git worktrees let you have multiple branches checked out simultaneously in separate directories. No stashing, no context switching, no 'just a second while I save my work.'
Anurag Verma
6 min read
Sponsored
The pattern is familiar: you’re deep in a feature branch, tests aren’t passing yet, and a Slack message arrives asking you to quickly check something on main or fix a bug on a release branch. You stash your work, switch branches, do the thing, switch back, pop the stash, and try to remember where your head was.
Git worktrees eliminate this entirely. A worktree is a second (or third, or fourth) checkout of the same repository in a separate directory. Each worktree has its own branch, its own working tree state, and its own HEAD. You switch between them by switching directories, not by switching branches.
The Basic Setup
# You're on feature/auth-rewrite in your main checkout
# You need to check something on main without disrupting your work
git worktree add ../project-main main
# Creates a new directory ../project-main checked out to main
# Work in the new directory
cd ../project-main
npm install # each worktree has its own node_modules
git log --oneline -5
# Come back to your feature work
cd ../project
# feature/auth-rewrite is still exactly where you left it
The ../project-main directory is a full checkout. You can run your dev server, run tests, make commits. It’s all linked to the same .git database, so you’re not duplicating the git history, just the working files.
Common Patterns
Reviewing a PR Without Touching Your Current Work
This is the most immediate use case. A colleague opens a PR and you want to run it locally:
# Fetch the branch
git fetch origin pr-branch-name
# Create a worktree for the review
git worktree add ../project-review origin/pr-branch-name
# Run it
cd ../project-review && npm install && npm run dev
When you’re done reviewing:
git worktree remove ../project-review
Parallel Feature Development
AI-assisted development makes this pattern especially useful. You’re generating and testing one feature while another generates. Each feature lives in its own worktree so tests can run independently:
git worktree add ../project-auth feature/new-auth
git worktree add ../project-payments feature/payment-rework
# Terminal 1
cd ../project-auth && npm test -- --watch
# Terminal 2
cd ../project-payments && npm test -- --watch
# Terminal 3
cd ../project && git log --all --oneline
No context switching. Each test suite runs against its own branch. Changes in one don’t bleed into the other.
Running the Previous Version While Working on the Next
Useful for debugging regressions: run the last stable release in one worktree while editing the current branch in another.
git worktree add ../project-stable v2.4.1
# The stable version runs at localhost:3001
cd ../project-stable && PORT=3001 npm run dev
# The development version runs at localhost:3000
cd ../project && npm run dev
# Compare behavior directly
Hotfix While Mid-Feature
The original problem the stash dance solves:
# You're mid-feature
git worktree add ../project-hotfix main
cd ../project-hotfix
git checkout -b hotfix/login-crash
# fix the bug
git commit -m "fix: null check on login response"
git push origin hotfix/login-crash
# open PR, merge, done
git worktree remove ../project-hotfix
# Back to your feature, undisturbed
cd ../project
Listing and Managing Worktrees
# See all worktrees
git worktree list
/home/user/project abc1234 [feature/auth-rewrite]
/home/user/project-main def5678 [main]
/home/user/project-review ghi9012 [pr-branch]
# Remove a worktree (the directory must be clean)
git worktree remove ../project-review
# Remove even if there are untracked files
git worktree remove --force ../project-review
# Prune stale worktree references (after manually deleting a directory)
git worktree prune
One constraint: the same branch cannot be checked out in two worktrees simultaneously. If you try, git rejects it. This prevents the confusing situation where changes in one worktree conflict with changes in another on the same branch.
Setting Up Node.js Projects in Worktrees
Each worktree has its own working directory, which means node_modules is independent. You need to run npm install (or pnpm install or bun install) in each worktree separately.
For large projects where npm install takes a while, this is the main overhead of worktrees. A few strategies:
Use pnpm with a shared store. pnpm’s content-addressed store is global, so pnpm install in a worktree re-uses cached packages rather than re-downloading them. The install is fast even for large dependency trees.
# In each worktree
pnpm install # fast: links from the global store
Skip the install if the branch has the same package.json. If you’re creating a worktree from a branch that hasn’t changed package.json relative to main, you can symlink node_modules:
git worktree add ../project-hotfix main
ln -s $(pwd)/node_modules ../project-hotfix/node_modules
This works as long as you don’t need different packages. If the branches diverge in dependencies, symlink becomes a problem.
Use direnv with per-worktree .envrc. If each worktree needs different environment variables (different ports, different DATABASE_URL), .envrc files that are gitignored make this easy:
# ../project-hotfix/.envrc
export PORT=3001
export DATABASE_URL=postgres://localhost/myapp_hotfix
Worktrees and Your Editor
Most editors handle worktrees well because they work with directories, not git checkouts specifically.
VS Code: Open a worktree directory with code /path/to/worktree. It opens as a separate window with its own workspace. The Source Control panel shows the branch and changes for that worktree. You can have four VS Code windows open, each on a different branch.
JetBrains IDEs: Open the worktree directory as a new project. Git operations work correctly within each window.
Neovim/vim: No special handling needed. Open files from the worktree directory normally.
The one case that gets awkward is if your editor opens the project root by looking for .git. Since all worktrees link back to the same .git directory, some tooling can get confused about which branch it’s on. This is rare in practice.
What Worktrees Don’t Do
Worktrees solve the “multiple branches at once” problem. They don’t replace:
- Feature branches for code isolation. Worktrees are a checkout mechanism, not a branching strategy. You still need separate branches for separate features.
- CI/CD. Running tests locally across worktrees is useful for quick feedback. Real validation still happens in CI.
- stash for quick temporary saves. If you just want to temporarily shelve two lines of debug code while you look at something,
git stashis faster than a worktree.
The worktree mental model is: each worktree is a separate concern that’s going to live long enough to warrant its own directory. For anything that takes more than a few minutes, a worktree is usually cleaner than a stash.
The Setup That Pays Off
For a team where everyone uses AI coding assistants to work on multiple things in parallel, worktrees become a natural part of the workflow. Set up aliases to make the common operations fast:
# ~/.gitconfig
[alias]
wt-add = "!f() { git worktree add \"../${PWD##*/}-$1\" $2; }; f"
wt-list = worktree list
wt-rm = worktree remove
# Usage
git wt-add hotfix main # creates ../project-hotfix on main
git wt-add review origin/pr-42 # creates ../project-review on a PR branch
git wt-list # see all active worktrees
git wt-rm ../project-hotfix # clean up when done
The stash dance is worth skipping. Worktrees are the cleaner option for anything that will take more than a few minutes.
Sponsored
More from this category
More from Web Development
Sponsored
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored