Skip to content

Web Development · Developer Tooling

Git Worktrees: Multiple Branches, No Stash Dance

Git worktrees give you multiple branches checked out in separate directories at once. No stashing, no context switching, no losing your place.

Anurag Verma

Anurag Verma

6 min read

Git Worktrees: Check Out Multiple Branches at Once Without the Stash Dance

Sponsored

Share

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 stash is 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.

Frequently asked questions

How is a worktree different from just cloning the repo twice?
A second clone duplicates the entire object database and has no relationship to the first, so fetches, branches and commits are independent and you have to keep them in sync manually. A worktree shares one .git directory, so a commit in one is immediately visible in the others, a fetch updates all of them, and branch state is consistent across the set. You get the separate working directories without the divergence.
Do I have to run npm install in every worktree?
Yes, because node_modules lives in the working directory and each worktree has its own. That is the real cost of the approach on large projects. pnpm reduces it substantially: its store is global and content-addressed, so installing in a new worktree mostly links packages that are already on disk rather than downloading them again. Symlinking node_modules from another worktree works too, but only while both branches want the same dependencies.
When is a worktree overkill?
When the interruption is measured in minutes. If you want to stash two lines of debugging while you glance at something, git stash is faster than creating a directory, installing dependencies and configuring an environment. The rough dividing line is whether the second task will live long enough to justify its own directory. Anything you will come back to more than once is worth a worktree.
What happens if I try to check out the same branch twice?
Git refuses, and that is deliberate. Two working directories on the same branch would each hold a different uncommitted state while pointing at the same ref, which produces conflicts that are genuinely confusing to diagnose. If you want two checkouts of the same code, create a branch for the second one, even a throwaway. The restriction is protecting you from a class of problem rather than being an arbitrary limit.
How do I handle per-worktree environment config?
Gitignored .envrc files with direnv are the cleanest option. Each worktree gets its own ports, database URL and any other environment that would collide if two branches ran their dev servers simultaneously, and direnv loads them automatically when you cd in. Without something like that, running two worktrees at once usually fails on a port conflict a minute after you start.

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored