Code Review Workflows with Git Worktrees

Architectural Foundation: Isolated Review Environments

Code review represents a critical quality gate in software development, yet traditional workflows introduce friction: developers must either stash uncommitted work, commit work-in-progress, or maintain complex mental models of which branch contains which code under review. Git worktrees eliminate these impediments by providing dedicated, persistent review environments that exist independently from active development workspaces.

Core Benefits for Code Review

Context Preservation: Reviewers can maintain multiple pull requests in separate worktrees simultaneously without mental context switching or losing work state. A reviewer examining authentication changes in one worktree while security patches await in another experiences no workflow disruption.

Executable Testing: Rather than reviewing code abstractly in browser interfaces, reviewers execute changes locally in isolated environments, catching integration issues, runtime errors, and performance regressions that static analysis cannot detect.

Persistent Review State: Traditional checkout-based review requires cleaning up after each review (deleting branches, stashing changes). Worktrees persist across reviews—simply switch branches within the review worktree without affecting development environment.

Reduced Cognitive Load: A dedicated review worktree creates clear mental separation between “code I’m writing” and “code I’m evaluating,” reducing errors from accidentally committing review-related changes to feature branches.


Pattern 1: Dedicated Long-Lived Review Worktree

Establish a permanent review workspace that persists across multiple review cycles.

Architecture

Development Workspace:
~/projects/application/           # Main development
  ├── feature-payment-gateway/   # Current feature work
  └── refactor-authentication/   # In-progress refactoring

Review Workspace:
~/projects/application-review/    # Persistent review environment
  # No feature branches here
  # Used exclusively for reviewing others' work

Initial Setup

# One-time setup: Create dedicated review worktree
cd ~/projects/application
git worktree add ../application-review

cd ../application-review

# Configure review-specific Git settings
git config user.email "[email protected]"  # Optional: Separate review identity

# Install development dependencies once
npm install

Review Workflow

# Review Workflow for PR #456
cd ~/projects/application-review

# Fetch pull request as local branch
git fetch origin pull/456/head:pr-456
git switch pr-456

# Execute review protocol
npm install  # Update dependencies if package.json changed
npm run lint
npm test
npm run build

# Start application for manual testing
npm run dev
# Application runs on localhost:3000
# Test authentication, payment flows, etc.

# Leave review comments in GitHub/GitLab web interface
# No need to commit anything locally

# Move to next review without cleanup
git fetch origin pull/789/head:pr-789
git switch pr-789
# Previous PR state discarded automatically

Key Advantage: Review worktree accumulates node_modules and build cache. Subsequent reviews skip dependency installation when package versions unchanged, saving 2-5 minutes per review.


Pattern 2: Automated Pull Request Review Worktree Creation

For teams performing high volumes of reviews, automate worktree creation triggered by pull request events.

GitHub Actions Integration

# .github/workflows/create-review-environment.yml
name: Create Review Environment

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  create-review-worktree:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
        with:
          fetch-depth: 0 # Full history needed for worktree operations

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "20"
          cache: "npm"

      - name: Create review worktree
        run: |
          mkdir -p /tmp/review-environments
          git worktree add /tmp/review-environments/pr-${{ github.event.pull_request.number }}
          cd /tmp/review-environments/pr-${{ github.event.pull_request.number }}
          git switch pr-${{ github.event.pull_request.number }}

      - name: Install dependencies
        run: |
          cd /tmp/review-environments/pr-${{ github.event.pull_request.number }}
          npm ci

      - name: Run automated checks
        run: |
          cd /tmp/review-environments/pr-${{ github.event.pull_request.number }}
          npm run lint > lint-report.txt 2>&1 || true
          npm test > test-report.txt 2>&1 || true
          npm run build > build-report.txt 2>&1 || true

      - name: Generate review summary
        run: |
          cd /tmp/review-environments/pr-${{ github.event.pull_request.number }}
          cat > REVIEW_SUMMARY.md << 'EOF'
          # Automated Review Summary

          **PR**: #${{ github.event.pull_request.number }}
          **Author**: @${{ github.event.pull_request.user.login }}
          **Branch**: `${{ github.event.pull_request.head.ref }}`

          ## Automated Checks

          ### Linting
          \`\`\`
          $(cat lint-report.txt)
          \`\`\`

          ### Tests
          \`\`\`
          $(cat test-report.txt)
          \`\`\`

          ### Build
          \`\`\`
          $(cat build-report.txt)
          \`\`\`
          EOF

      - name: Comment on PR with review environment details
        uses: actions/github-script@v6
        with:
          script: |
            const fs = require('fs');
            const summary = fs.readFileSync('/tmp/review-environments/pr-${{ github.event.pull_request.number }}/REVIEW_SUMMARY.md', 'utf8');

            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: summary
            });

      - name: Upload review artifacts
        uses: actions/upload-artifact@v3
        with:
          name: review-pr-${{ github.event.pull_request.number }}
          path:
            /tmp/review-environments/pr-${{ github.event.pull_request.number }}

Pattern 3: Shared Review Worktree Server

Large teams benefit from centralized review infrastructure where reviewers access shared worktrees via SSH rather than maintaining local copies.

Infrastructure Architecture

Review Server: review-server.company.internal

Directory Structure:
/opt/review-worktrees/
├── application.git/          # Bare repository (shared object database)
├── pr-456/                   # Review worktree for PR #456
├── pr-457/                   # Review worktree for PR #457
└── pr-458/                   # Review worktree for PR #458

Access Pattern:
Reviewer → SSH → review-server.company.internal → /opt/review-worktrees/pr-NNN/

Server Setup

# Run on dedicated review server
sudo mkdir -p /opt/review-worktrees
sudo chown -R git-reviews:developers /opt/review-worktrees
sudo chmod 2775 /opt/review-worktrees  # setgid for group inheritance

# Clone bare repository
cd /opt/review-worktrees
git clone --bare [email protected]:company/application.git application.git

cd application.git
git config core.sharedRepository group

Automated Review Worktree Creation Script

#!/bin/bash
# /opt/scripts/create-review-worktree.sh
# Triggered by webhook on PR creation

set -euo pipefail

PR_NUMBER="$1"
AUTHOR="$2"
PR_BRANCH="$3"

REVIEW_BASE="/opt/review-worktrees"
REPO_PATH="${REVIEW_BASE}/application.git"
WORKTREE_PATH="${REVIEW_BASE}/pr-${PR_NUMBER}"

# Check if worktree already exists
if [ -d "$WORKTREE_PATH" ]; then
    echo "Worktree for PR #${PR_NUMBER} already exists. Updating..."
    cd "$WORKTREE_PATH"
    git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}"
    git reset --hard "pr-${PR_NUMBER}"
    exit 0
fi

cd "$REPO_PATH"

# Fetch PR reference
git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}"

# Create dedicated review worktree
git worktree add "$WORKTREE_PATH" "pr-${PR_NUMBER}"

cd "$WORKTREE_PATH"

# Setup review environment
export PNPM_HOME="${REVIEW_BASE}/.pnpm-store"
pnpm install

# Run automated pre-checks
pnpm run lint > "${WORKTREE_PATH}/lint-report.txt" 2>&1 || true
pnpm run test > "${WORKTREE_PATH}/test-report.txt" 2>&1 || true

# Generate review metadata
cat > "${WORKTREE_PATH}/REVIEW_INFO.md" << EOF
# Code Review Environment

**PR Number**: #${PR_NUMBER}
**Author**: ${AUTHOR}
**Branch**: ${PR_BRANCH}
**Created**: $(date -Iseconds)
**Server**: review-server.company.internal
**Path**: ${WORKTREE_PATH}

## Quick Access

\`\`\`bash
# SSH into review server
ssh review-server.company.internal

# Navigate to review worktree
cd ${WORKTREE_PATH}

# Start development server
pnpm run dev

# Run tests
pnpm test

# View automated check results
cat lint-report.txt
cat test-report.txt
\`\`\`

## Automated Check Results

$(cat lint-report.txt)

---

$(cat test-report.txt)
EOF

# Set proper permissions
chgrp -R developers "$WORKTREE_PATH"
chmod -R g+rwX "$WORKTREE_PATH"

echo "✅ Review environment created: ${WORKTREE_PATH}"
echo "   Access via: ssh review-server.company.internal"
echo "   Path: ${WORKTREE_PATH}"

# Notify team (integrate with Slack, email, etc.)
curl -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL \
  -H 'Content-Type: application/json' \
  -d "{\"text\":\"Review environment ready for PR #${PR_NUMBER} by ${AUTHOR}\n\`ssh review-server.company.internal\` → \`cd ${WORKTREE_PATH}\`\"}"

Reviewer Access Workflow

# Reviewer's local machine
ssh review-server.company.internal

# On review server
cd /opt/review-worktrees/pr-456

# Review code
cat REVIEW_INFO.md  # Read setup instructions

# Run application
pnpm run dev
# Application accessible at http://review-server.company.internal:3456

# Test changes manually
curl http://localhost:3456/api/health
curl http://localhost:3456/api/users

# Leave review comments via GitHub/GitLab web interface
# No git commits needed locally

Pattern 4: IDE-Integrated Review Workflows

Modern IDEs support opening multiple worktrees as separate projects, enabling efficient context switching during review.

Visual Studio Code Configuration

Multi-Root Workspace for Reviews:

// review-workspace.code-workspace
{
  "folders": [
    {
      "name": "Development",
      "path": "/Users/developer/projects/application"
    },
    {
      "name": "Review: PR #456",
      "path": "/Users/developer/projects/application-review"
    }
  ],
  "settings": {
    // Review worktree is read-only
    "files.readonly": {
      "**/application-review/**": true
    },

    // Different linting rules for review
    "[application-review]": {
      "editor.formatOnSave": false,
      "editor.quickSuggestions": false
    },

    // Distinct terminal colors
    "workbench.colorCustomizations": {
      "[application-review]": {
        "terminal.background": "#1a1a2e"
      }
    }
  },
  "launch": {
    "version": "0.2.0",
    "configurations": [
      {
        "type": "node",
        "request": "launch",
        "name": "Run Development",
        "cwd": "${workspaceFolder:Development}",
        "program": "${workspaceFolder:Development}/src/index.js"
      },
      {
        "type": "node",
        "request": "launch",
        "name": "Run Review PR #456",
        "cwd": "${workspaceFolder:Review: PR #456}",
        "program": "${workspaceFolder:Review: PR #456}/src/index.js",
        "env": {
          "NODE_ENV": "review",
          "PORT": "3001"
        }
      }
    ]
  }
}

Task Configuration for Reviews:

// .vscode/tasks.json (in review worktree)
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Review: Fetch Latest PR",
      "type": "shell",
      "command": "git fetch origin pull/${input:prNumber}/head:pr-${input:prNumber} && git switch pr-${input:prNumber}",
      "problemMatcher": []
    },
    {
      "label": "Review: Run Checks",
      "type": "shell",
      "command": "npm run lint && npm test && npm run build",
      "problemMatcher": ["$eslint-compact", "$tsc"],
      "group": {
        "kind": "test",
        "isDefault": true
      }
    },
    {
      "label": "Review: Generate Diff Summary",
      "type": "shell",
      "command": "git diff --stat origin/main > REVIEW_DIFF.txt && echo 'Diff summary saved to REVIEW_DIFF.txt'",
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "prNumber",
      "type": "promptString",
      "description": "Enter PR number to review"
    }
  ]
}

Pattern 5: Comparative Review (Side-by-Side Diff Testing)

When reviewing complex changes, reviewers benefit from running both the original and proposed implementations simultaneously for direct comparison.

Setup

# Create two worktrees: baseline and proposed changes
cd ~/projects/application

# Baseline: Current production code
git worktree add ../application-baseline main

# Proposed: PR changes
git worktree add ../application-pr-review
cd ../application-pr-review
git fetch origin pull/456/head:pr-456
git switch pr-456

Comparative Testing Script

#!/bin/bash
# comparative-review.sh
# Run baseline and PR implementation side-by-side

set -euo pipefail

BASELINE_DIR="../application-baseline"
PR_DIR="../application-pr-review"

echo "Starting comparative review..."
echo "=============================="

# Start baseline application on port 3000
echo "Starting baseline (main branch) on port 3000..."
(cd "$BASELINE_DIR" && PORT=3000 npm start > baseline.log 2>&1) &
BASELINE_PID=$!

# Wait for baseline to be ready
sleep 5

# Start PR application on port 3001
echo "Starting PR implementation on port 3001..."
(cd "$PR_DIR" && PORT=3001 npm start > pr.log 2>&1) &
PR_PID=$!

# Wait for PR to be ready
sleep 5

echo ""
echo "Applications running:"
echo "  Baseline (main): http://localhost:3000"
echo "  PR #456:         http://localhost:3001"
echo ""
echo "Press Ctrl+C to stop both applications"

# Trap Ctrl+C to cleanup
trap "kill $BASELINE_PID $PR_PID; exit" INT TERM

# Keep script running
wait

Automated Performance Comparison

#!/bin/bash
# performance-comparison.sh
# Benchmark baseline vs PR implementation

BASELINE_URL="http://localhost:3000"
PR_URL="http://localhost:3001"
ENDPOINT="/api/users"

echo "Performance Comparison: Baseline vs PR"
echo "======================================"

# Benchmark baseline
echo "Benchmarking baseline..."
BASELINE_TIME=$(curl -o /dev/null -s -w '%{time_total}\n' "${BASELINE_URL}${ENDPOINT}")

# Benchmark PR
echo "Benchmarking PR..."
PR_TIME=$(curl -o /dev/null -s -w '%{time_total}\n' "${PR_URL}${ENDPOINT}")

# Compare
IMPROVEMENT=$(echo "scale=2; (($BASELINE_TIME - $PR_TIME) / $BASELINE_TIME) * 100" | bc)

echo ""
echo "Results:"
echo "  Baseline response time: ${BASELINE_TIME}s"
echo "  PR response time:       ${PR_TIME}s"
echo "  Performance change:     ${IMPROVEMENT}%"

if (( $(echo "$PR_TIME < $BASELINE_TIME" | bc -l) )); then
    echo "  ✅ PR improves performance"
else
    echo "  ⚠️  PR degrades performance"
fi

Automation: Review Workflow Scripts

Script 1: Quick PR Review Setup

#!/bin/bash
# quick-review.sh
# Rapidly setup review environment for a PR

set -euo pipefail

PR_NUMBER="$1"

if [ -z "$PR_NUMBER" ]; then
    echo "Usage: $0 <PR_NUMBER>"
    exit 1
fi

REVIEW_DIR="../application-review"

# Ensure review worktree exists
if [ ! -d "$REVIEW_DIR" ]; then
    echo "Creating review worktree..."
    git worktree add "$REVIEW_DIR"
fi

cd "$REVIEW_DIR"

# Fetch and checkout PR
echo "Fetching PR #${PR_NUMBER}..."
git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}"
git switch "pr-${PR_NUMBER}"

# Update dependencies if needed
if git diff --name-only HEAD~1 | grep -q "package.json"; then
    echo "package.json changed, updating dependencies..."
    npm install
fi

# Run automated checks
echo "Running automated checks..."
npm run lint 2>&1 | tee lint-output.txt
npm test 2>&1 | tee test-output.txt

# Generate review checklist
cat > REVIEW_CHECKLIST.md << EOF
# Review Checklist for PR #${PR_NUMBER}

## Automated Checks
- [ ] Linting: $(grep -q "error" lint-output.txt && echo "❌ FAILED" || echo "✅ PASSED")
- [ ] Tests: $(grep -q "FAIL" test-output.txt && echo "❌ FAILED" || echo "✅ PASSED")

## Manual Review
- [ ] Code quality and readability
- [ ] Security considerations
- [ ] Performance implications
- [ ] Test coverage adequate
- [ ] Documentation updated
- [ ] Breaking changes documented

## Testing
- [ ] Functionality works as expected
- [ ] Edge cases handled
- [ ] Error handling appropriate
- [ ] UI/UX acceptable (if applicable)

## Notes
<!-- Add review notes here -->

EOF

echo ""
echo "✅ Review environment ready for PR #${PR_NUMBER}"
echo "   Directory: $REVIEW_DIR"
echo "   Checklist: REVIEW_CHECKLIST.md"
echo ""
echo "Next steps:"
echo "  1. Review code changes: git diff origin/main"
echo "  2. Test application: npm run dev"
echo "  3. Complete checklist: vim REVIEW_CHECKLIST.md"

Usage:

./quick-review.sh 456

# Output:
# Creating review worktree...
# Fetching PR #456...
# Running automated checks...
# ✅ Review environment ready for PR #456
#    Directory: ../application-review
#    Checklist: REVIEW_CHECKLIST.md

Script 2: Multi-PR Review Queue

#!/bin/bash
# review-queue.sh
# Manage queue of PRs for review

QUEUE_FILE="$HOME/.review-queue"
REVIEW_DIR="../application-review"

show_queue() {
    if [ ! -f "$QUEUE_FILE" ]; then
        echo "Review queue is empty"
        return
    fi

    echo "Review Queue:"
    echo "============="
    cat -n "$QUEUE_FILE"
}

add_to_queue() {
    local pr_number="$1"
    echo "$pr_number" >> "$QUEUE_FILE"
    echo "Added PR #${pr_number} to review queue"
}

next_review() {
    if [ ! -f "$QUEUE_FILE" ] || [ ! -s "$QUEUE_FILE" ]; then
        echo "Review queue is empty"
        return
    fi

    # Get first PR in queue
    local pr_number=$(head -n 1 "$QUEUE_FILE")

    # Remove from queue
    tail -n +2 "$QUEUE_FILE" > "${QUEUE_FILE}.tmp"
    mv "${QUEUE_FILE}.tmp" "$QUEUE_FILE"

    echo "Reviewing PR #${pr_number}..."
    ./quick-review.sh "$pr_number"
}

case "${1:-}" in
    show)
        show_queue
        ;;
    add)
        add_to_queue "$2"
        ;;
    next)
        next_review
        ;;
    *)
        echo "Usage: $0 {show|add <PR_NUMBER>|next}"
        exit 1
        ;;
esac

Usage:

# Add PRs to review queue
./review-queue.sh add 456
./review-queue.sh add 457
./review-queue.sh add 458

# View queue
./review-queue.sh show
# Review Queue:
# =============
#      1  456
#      2  457
#      3  458

# Review next PR in queue
./review-queue.sh next
# Reviewing PR #456...
# ✅ Review environment ready for PR #456

Best Practices: Code Review with Worktrees

✅ Recommended Practices

  1. Persistent Review Worktree: Create once, reuse for all reviews. Saves setup time and maintains consistent environment.

  2. Automated Pre-Checks: Run linting and tests automatically before manual review to catch obvious issues quickly.

  3. Review Checklists: Generate standardized checklists for consistency across reviews.

  4. Parallel Comparison Testing: When reviewing performance-critical changes, run baseline and PR implementations side-by-side.

  5. Separate Review Identity: Configure distinct Git identity in review worktree to avoid accidentally committing from wrong context.

⚠️ Anti-Patterns to Avoid

  1. Committing in Review Worktree: Review worktrees are for inspection, not modification. All comments should be via PR interface.

  2. Stale Review Environments: Periodically update base dependencies in review worktree to match main branch.

  3. Shared Review Worktrees: Each reviewer should have their own review worktree to avoid conflicts.

  4. Forgetting Cleanup: After PR merges, remove the corresponding branch from review worktree to avoid confusion.


Troubleshooting: Common Review Workflow Issues

Issue 1: Review Worktree Dependency Drift

Symptom: Review worktree has outdated dependencies, causing false test failures.

Solution: Periodic dependency synchronization

# In review worktree
git fetch origin main
git switch main
npm install  # Update to latest main dependencies
git switch pr-456  # Back to reviewing

Issue 2: Port Conflicts During Comparative Testing

Symptom: Cannot run baseline and PR implementations simultaneously due to port conflict.

Solution: Use environment variables for dynamic port assignment

# Baseline
PORT=3000 npm start &

# PR
PORT=3001 npm start &

Issue 3: Slow Review Environment Setup

Symptom: Creating review worktree takes several minutes due to dependency installation.

Solution: Use pnpm or Yarn with shared cache

# Configure shared dependency cache
pnpm config set store-dir ~/.pnpm-store

# In review worktree
pnpm install  # Reuses cached packages

Summary: Streamlined Code Review with Worktrees

Key Benefits:

  • Setup Time: 70-90% faster review environment creation
  • Context Switching: Zero overhead when moving between development and review
  • Review Quality: Executable testing catches integration issues static analysis misses
  • Team Velocity: Reviewers can maintain queues of PRs without workflow disruption

When to Use Worktree-Based Review:

  • ✅ Team performing >5 code reviews per week
  • ✅ Reviews require running applications locally
  • ✅ Large PRs needing extended review time
  • ✅ Performance-critical changes requiring comparative testing

When Traditional Review May Suffice:

  • ❌ Small documentation-only PRs
  • ❌ Teams primarily doing static code review without execution
  • ❌ Single-reviewer projects where parallelism isn’t needed

← CI/CD Integration | Disk Space Management →