Reviewing a pull request in a second worktree Jump to heading
Reading a diff in a browser tells you what changed. Running the branch tells you whether it works — and for anything involving state, timing or a user interface, the second question is the one that matters. The obstacle is that checking out someone else’s branch means abandoning your own working state, so most reviews stay at the reading stage. A detached worktree removes the obstacle entirely, as part of Git worktrees and parallel development.
When to use this approach Jump to heading
- The change affects behaviour you cannot evaluate by reading.
- You have uncommitted work you do not want to stash.
- You want to run the branch’s tests against your own environment.
- The change is large enough that navigating it in an editor beats a web diff.
- If the change is three lines of configuration, read it in the browser and save the ceremony.
Step 1 — Fetch the pull request ref Jump to heading
Forges expose pull requests as refs, including those from forks, so you do not need the contributor’s remote.
# Fetch a specific pull request into a local ref
git fetch origin 'pull/812/head:refs/remotes/origin/pr/812'
# Or configure the refspec once and fetch them all
git config --add remote.origin.fetch '+refs/pull/*/head:refs/remotes/origin/pr/*'
git fetch origin # Verification: the ref exists and points where you expect
git rev-parse --short origin/pr/812
git log --oneline -1 origin/pr/812 The second form is worth doing once per clone. After it, every pull request is available as origin/pr/<number> without another fetch command to remember.
Step 2 — Create a detached worktree for it Jump to heading
Detached is deliberate: you are not going to commit here, and not claiming a branch keeps the review worktree from blocking anyone else’s checkout.
git worktree add --detach ../review-812 origin/pr/812
cd ../review-812 # Verification: you are on the pull request's commit, with your own work untouched
git log --oneline -1
git -C ../app status --short | head -3 # your original worktree, unchanged Step 3 — Read the diff against the merge base, not the tip Jump to heading
Comparing against your local default branch shows changes that came from elsewhere. The merge base is what the author actually changed.
base=$(git merge-base origin/main HEAD)
# The change itself
git diff --stat "$base"...HEAD
git diff "$base"...HEAD -- ':!*.lock' ':!**/generated/**' # Commit by commit, which is how the author intended it to be read
git log --oneline --reverse "$base"..HEAD
git show --stat "$(git rev-list --reverse "$base"..HEAD | head -1)" The three-dot form is the important detail: base...HEAD shows what the branch introduced, while base..HEAD in a diff context shows something subtly different and usually not what a reviewer wants.
Step 4 — Run it Jump to heading
This is the whole point of the exercise, and it is where the ignored-files problem from the parent topic appears.
# Dependencies are not shared between worktrees
npm ci
# Ignored local configuration has to be copied
cp ../app/.env.local .env.local 2>/dev/null || true
# Then run whatever answers your question
npm test -- --findRelatedTests $(git diff --name-only "$base"...HEAD)
npm run dev # Verification: the branch's own test suite passes in your environment
npm test 2>&1 | tail -5 Step 5 — Leave feedback and clean up Jump to heading
# Comment from the terminal while the context is fresh
gh pr review 812 --comment --body "Ran it locally; the retry path loops when the upstream returns 503. Repro: …" cd ../app
git worktree remove ../review-812
git worktree list # Verification: nothing stale remains
git worktree prune --verbose && git worktree list --porcelain | grep -c '^worktree' SAFETY WARNING — a review worktree runs code from a branch you have not read yet, with your credentials and your environment. Dependency installation alone executes lifecycle scripts from the contributor’s lockfile. For contributions from outside your team, install with scripts disabled, review the dependency diff before running anything, and consider a container rather than your own machine.
# The cautious install for an untrusted branch
npm ci --ignore-scripts
git diff "$base"...HEAD -- package.json package-lock.json Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Why detached rather than checking out the branch? Jump to heading
Because a review is read-only and a detached worktree does not claim the branch. If you check it out and the author force-pushes, you are left reconciling a branch you never intended to own. Detached, a re-fetch and a git checkout --detach gets you the new tip with nothing to reconcile.
How do I review the second version after a force-push? Jump to heading
Fetch again and compare the two tips with git range-diff, which shows how the commits themselves changed rather than re-showing the whole branch. The technique is covered in reviewing a force-push with git range-diff.
Can I keep one review worktree and reuse it? Jump to heading
Yes, and it is a good habit: a single ../review directory that you re-point with git checkout --detach origin/pr/<n> avoids accumulating directories and keeps its dependency install warm. Only the dependency tree needs refreshing when the lockfile differs.
Related Jump to heading
- Git Worktrees & Parallel Development — the parent topic and what worktrees share.
- Reviewing a Force-Push With git range-diff — reading the second version of a branch.
- Running Long Builds in a Separate Worktree — the same isolation applied to your own work.