Tools/Git

Git Worktrees Changed How I Review PRs

I used to stash my work, check out a branch to review it, then unstash and hope nothing broke. Worktrees ended that entire dance.

For years my review workflow was: git stash, check out the PR branch, review, run it locally, check out my branch again, git stash pop, and cross my fingers that nothing in between had drifted. It worked, but it interrupted whatever I was actually building, and every so often the stash pop wasn't as clean as I expected.

One Repo, One Branch, Forever

The root problem is that a normal git checkout can only have one branch active at a time in a given working directory. Reviewing someone else's branch necessarily means leaving your own mid-thought. git worktree removes that constraint by letting you check out multiple branches into separate directories that all share the same .git history:

git worktree add ../review-pr-482 origin/feature/pr-482
cd ../review-pr-482
npm install
npm run dev

That's a fully separate working directory with its own file state, running against the same repository. My original branch, in my original directory, is untouched — no stash, no risk of losing uncommitted work, and I can literally run both versions side by side if I want to compare behavior directly.

Cleaning Up Without Losing Track

The one thing that took getting used to was that worktrees don't disappear on their own — you end up with a handful of directories if you're not deliberate about cleaning them up. git worktree list keeps them visible, and removal is explicit:

git worktree list
# /Users/me/project           abc1234 [main]
# /Users/me/review-pr-482     def5678 [feature/pr-482]

git worktree remove ../review-pr-482

I settled on a convention of naming review worktrees review-pr-<number> specifically so git worktree list reads like a small changelog of what I've been looking at recently, and so a stray rm -rf ../review-pr-* doesn't accidentally catch a worktree I actually meant to keep.

A Small Script to Make It a One-Liner

Typing the full worktree add command with the right remote and branch naming got tedious fast, so I wrapped it:

review() {
  local pr="$1"
  git fetch origin "pull/${pr}/head:pr-${pr}"
  git worktree add "../review-pr-${pr}" "pr-${pr}"
  cd "../review-pr-${pr}" || return
}

Now review 482 fetches the PR ref, creates the worktree, and drops me into it in one command. Cleanup afterward is git worktree remove plus git branch -D pr-482 to drop the local ref once I'm done.

Where It Also Helps Beyond Review

The same trick turned out to be useful for CI debugging too — when a reproducible build pipeline fails on a specific commit, checking that exact commit into its own worktree lets me poke at it without disturbing whatever branch I'm actively working on, and without the round trip of committing or stashing in-progress changes just to go look at something else. It's a small tool, but it removed a genuinely annoying context-switch tax that I'd just accepted as normal for years.

One Gotcha: Shared State Isn't Actually Shared

The thing that surprised me at first is that worktrees share git history but nothing else — each one needs its own npm install, its own .env, its own build artifacts. I lost a few minutes the first time a review worktree failed to start because I'd assumed node_modules would somehow be shared with my main checkout. It isn't, and for good reason: two branches can easily depend on different package versions, and silently sharing installed dependencies between them would reintroduce exactly the kind of state-bleed problem worktrees are supposed to eliminate. Budgeting the extra thirty seconds for a fresh install per worktree turned out to be a small price for the isolation it buys.