Here’s a workflow I see all the time. You’re deep in a feature branch. The dev server is running, your editor has a dozen tabs open with unstaged changes, your test watcher is humming, and then a Slack message arrives: “Hey, can you take a quick look at this PR?”
What you do next depends on how much git you’ve fought with in your career. The naive answer is git stash, git checkout other-branch, look at the PR, git checkout -, git stash pop, and pray nothing collided. The medium-experienced answer is a separate clone of the repo somewhere else. The right answer — the one almost nobody uses, despite it being built into git for a decade — is git worktree.
I’ve been using worktrees full-time for about a year now. I do not stash anymore. My dev server doesn’t get killed when I review someone’s PR. And I don’t have to maintain three separate clones of the same repo. Here is the workflow.
What a Worktree Actually Is
A git worktree is a separate working directory tied to the same .git repository. The repo itself — all the objects, refs, history — lives in one place. The checkouts can live anywhere, and each worktree has its own branch, its own working files, and its own state.
So instead of git checkout other-branch (which mutates your current directory), you say git worktree add ../my-project-other-branch other-branch, and now you have two directories, both backed by the same repo, each on a different branch. Edit in one, edit in the other, they don’t collide.
Setup
Most projects have a layout like this:
~/code/my-project/
The cleanest worktree layout I’ve found puts the main checkout inside a parent directory, with sibling directories for each worktree:
~/code/my-project/
├── main/ # main branch
├── feature-x/ # feature branch
└── pr-review/ # disposable worktree for reviewing PRs
If you’re starting from scratch:
mkdir ~/code/my-project && cd ~/code/my-project
git clone --bare [email protected]:owner/repo.git .bare
echo "gitdir: ./.bare" > .git
git worktree add main main
The --bare clone means there’s no working directory at the top level — just the repo internals. Then git worktree add main main creates the first real checkout. From here, every additional branch is a new worktree.
If you have an existing clone, you don’t need to start over. Just cd into your existing project and:
git worktree add ../my-project-feature-x feature-x
That’s it. Worktrees are additive. They don’t disturb your current checkout.
The Daily Workflow
Here’s what an actual day looks like.
Start a new feature. I’m working on main, I want to start feature/inbox-search:
cd ~/code/my-project/main
git fetch
git worktree add ../feature-inbox-search -b feature/inbox-search origin/main
cd ../feature-inbox-search
I’m now in a fresh directory on a fresh branch, with no impact on the main worktree. Open my editor here, start the dev server here, work here.
Review a PR while my feature is in flight. Slack message arrives. I need to look at PR #543, which is the branch bugfix/timezone-parsing:
cd ~/code/my-project
git worktree add pr-543 origin/bugfix/timezone-parsing
cd pr-543
# read the diff, check out the code, run the tests
Nothing in feature-inbox-search has been touched. My dev server there is still running. When I’m done reviewing:
cd ..
git worktree remove pr-543
Gone.
Hop to a long-running branch. Sometimes I have a heavy refactor branch that I poke at on Fridays. It lives at ~/code/my-project/refactor-pricing/. I just cd into it. No checkout, no stash, no anything. It’s just a directory.
Why This Is Better Than Stash + Checkout
Three concrete reasons:
- Dev servers don’t die. Most modern dev servers (Vite, Next.js, Rails, Phoenix) hot-reload aggressively but get confused when files change under them en masse.
git checkoutchanges hundreds of files at once. Worktrees don’t. - You don’t lose editor state. Editor tabs, terminal split panes, REPL state, debugger sessions — all of these are tied to a directory. Switching branches in the same directory blows them up. Switching to a different worktree doesn’t.
- Stash is dangerous. Stash is fine for a five-second context switch, but the moment you have more than two or three stashes, they become anonymous and easy to lose. I have shipped bugs because I forgot a stash existed. With worktrees, the in-progress work is in a named directory. You can’t forget it.
The Gotchas
A handful of small things to know.
You can’t check out the same branch in two worktrees. Git prevents this — and it’s the right call, because two checkouts of the same branch can’t both be sources of truth. If you need to, use a detached HEAD: git worktree add /tmp/peek HEAD~5.
Submodules are awkward. If your project has submodules, you’ll need git submodule update --init --recursive in each worktree the first time. Not the end of the world but worth knowing.
Node modules per worktree. node_modules is per-directory, so each worktree gets its own. This is by design — different branches may have different dependencies — but it eats disk space. pnpm handles this with a content-addressed store, so adopt pnpm if you haven’t.
Hooks copy correctly, but .envrc and untracked files don’t. Your .git/hooks/ directory is shared (it lives in the parent repo). But untracked files like .env.local need to be copied per worktree. I keep a small script that copies my untracked dotfiles into a new worktree on creation.
git worktree list is your friend. Run it any time you forget what worktrees you’ve got open. Run git worktree prune to clean up after you’ve manually deleted a worktree directory without using git worktree remove.
A Useful Wrapper
I have a tiny shell function I use to make new worktrees. Adapt to your shell:
function wt() {
local branch="$1"
local dirname="${branch//\//-}"
local root
root=$(git rev-parse --show-toplevel)/..
git -C "$root" worktree add "$root/$dirname" -b "$branch" origin/main
cd "$root/$dirname"
}
Run wt feature/whatever and it creates the branch, the worktree, and cd’s in. That’s the whole interface.
When Not to Use Worktrees
For tiny one-off branch switches — “let me check what’s on main real quick” — a plain git checkout is faster. Worktrees are for situations where you’d otherwise stash, where you’d otherwise lose dev server state, where you’d otherwise want a second clone of the repo. They’re not a replacement for checkout; they’re a complement to it.
Try It
If you’ve never used worktrees, the activation energy is low and the payoff is high. Take a project you work on daily, restructure it once into the bare-plus-worktrees layout, and use it for a week. You’ll stop reaching for git stash, and you’ll wonder why this isn’t the default workflow taught to every new git user.
It’s not a new feature. It’s been in git since 2015. It just doesn’t get the press it deserves.