← All articles
TOOLS Code Review Tools and Practices: From GitHub PRs to ... 2026-02-09 · 10 min read · code-review · github · pull-requests

Code Review Tools and Practices: From GitHub PRs to Stacked Diffs

Tools 2026-02-09 · 10 min read code-review github pull-requests graphite stacked-prs developer-tools

Code Review Tools and Practices: From GitHub PRs to Stacked Diffs

Code review is the highest-leverage quality practice in software engineering. It catches bugs, spreads knowledge, and improves code quality -- but only when done well. Bad code review is a bottleneck that slows teams to a crawl. This guide covers the tools that make review efficient and the practices that make it effective.

The State of Code Review Tools

Tool Type Best For Cost
GitHub Pull Requests Built-in Most teams already using GitHub Free with GitHub
Graphite Stacked PRs Teams that want stacked diffs on GitHub Free tier + paid
Reviewable Enhanced review Detailed review tracking Free for open source
GitLab Merge Requests Built-in GitLab users Free with GitLab
Gerrit Change-based Large open-source projects Free (OSS)
Phabricator (Phorge) Differential Companies wanting stacked diffs Free (OSS, archived)
Codestream In-editor review Teams wanting to review in IDE Free

GitHub Pull Requests: The Baseline

GitHub PRs are the default code review tool for most teams. Out of the box they're decent, but a few configurations and habits make them significantly better.

PR Templates

<!-- .github/pull_request_template.md -->
## Summary
<!-- What does this PR do and why? -->

## Changes
<!-- Key changes, organized by area -->
-

## Testing
<!-- How was this tested? -->
- [ ] Unit tests added/updated
- [ ] Manual testing completed
- [ ] Edge cases considered

## Screenshots
<!-- If applicable, add screenshots or screen recordings -->

## Notes for Reviewers
<!-- Anything the reviewer should pay attention to -->

Branch Protection Rules

# Settings -> Branches -> Branch protection rules for "main"
Required reviews: 1  # (or 2 for critical repos)
Dismiss stale PR reviews: Yes
Require review from code owners: Yes
Require status checks before merging:
  - tests
  - lint
  - build
Require branches to be up to date: Yes
Require linear history: Yes  # (forces rebase or squash merging)

CODEOWNERS File

Automatically assign reviewers based on file paths:

# .github/CODEOWNERS

# Default owners for everything
* @engineering-team

# Frontend
/src/components/ @frontend-team
/src/pages/ @frontend-team
*.css @frontend-team
*.tsx @frontend-team

# Backend
/src/api/ @backend-team
/src/services/ @backend-team

# Infrastructure
/terraform/ @platform-team
/docker/ @platform-team
/.github/ @platform-team

# Database migrations require DBA review
/prisma/migrations/ @dba-team @backend-team

# Security-sensitive files
/src/auth/ @security-team
/src/middleware/auth* @security-team

GitHub CLI for PR Workflows

# Create a PR
gh pr create --title "Add user authentication" --body "Implements JWT-based auth"

# View PR details
gh pr view 42

# Check out a PR locally
gh pr checkout 42

# Review a PR
gh pr review 42 --approve
gh pr review 42 --request-changes --body "Need to handle the null case in line 45"
gh pr review 42 --comment --body "Looks good, minor nit on naming"

# Merge a PR
gh pr merge 42 --squash --delete-branch

# List PRs awaiting your review
gh pr list --search "review-requested:@me"

# List your open PRs
gh pr list --author @me

Useful GitHub PR Search Queries

# PRs waiting for your review
review-requested:@me is:open

# PRs you need to follow up on
involves:@me is:open review:changes_requested

# Old PRs that might be stale
is:open sort:updated-asc created:<2026-01-01

# PRs that touch database migrations
is:open path:prisma/migrations

# Large PRs that need extra attention
is:open size:>500

Stacked PRs: The Better Workflow

The biggest problem with GitHub PRs is that they encourage large, monolithic changes. You build a feature, submit one big PR, wait for review, and nothing can proceed in parallel. Stacked PRs solve this by breaking work into small, dependent PRs that can be reviewed independently.

The Problem with Large PRs

# A single PR with 40 files changed and 800 lines:
- Reviewers skim (or rubber-stamp)
- Feedback is delayed because review takes an hour
- Merge conflicts accumulate
- Bug probability increases with PR size

# The same work as 4 stacked PRs of 200 lines each:
- Each PR is focused and reviewable in 15 minutes
- Earlier PRs can be approved while later ones are still in progress
- Conflicts are smaller and easier to resolve
- Bugs are caught at a finer granularity

Graphite: Stacked PRs for GitHub

Graphite is the most polished tool for stacked PRs on GitHub. It manages the branch dependencies, rebases automatically when parent PRs are updated, and provides a dashboard that makes the stack visible.

# Install Graphite CLI
npm install -g @withgraphite/graphite-cli
gt auth  # Authenticate with GitHub

# Start a stack from main
gt checkout main

# Create first PR in the stack
gt branch create add-user-model
# ... make changes ...
gt commit create -m "Add User model and migration"
gt stack submit  # Creates PR for this branch

# Build on top
gt branch create add-user-api
# ... make changes ...
gt commit create -m "Add user CRUD API endpoints"
gt stack submit  # Creates PR, marks dependency on previous PR

# Build on top again
gt branch create add-user-ui
# ... make changes ...
gt commit create -m "Add user management UI"
gt stack submit  # Creates PR, marks dependency

# View the stack
gt log
# main
# ├── add-user-model (PR #42 - approved)
# │   ├── add-user-api (PR #43 - in review)
# │   │   └── add-user-ui (PR #44 - in review)

# When the first PR is approved, merge from the bottom
gt stack submit --merge  # Merges approved PRs from the bottom up

# After parent PR is merged, rebase children
gt stack restack

Graphite Dashboard

Graphite provides a web dashboard that shows:

Manual Stacked PRs (Without Graphite)

If you don't want to use Graphite, you can manage stacked PRs manually:

# Create the first branch
git checkout -b feature/step-1 main
# ... make changes, commit ...
git push -u origin feature/step-1
gh pr create --base main --title "Step 1: Add data model"

# Create the second branch ON TOP of the first
git checkout -b feature/step-2 feature/step-1
# ... make changes, commit ...
git push -u origin feature/step-2
gh pr create --base feature/step-1 --title "Step 2: Add API endpoints"

# Create the third branch ON TOP of the second
git checkout -b feature/step-3 feature/step-2
# ... make changes, commit ...
git push -u origin feature/step-3
gh pr create --base feature/step-2 --title "Step 3: Add UI"

The manual approach works but requires careful rebasing when parent PRs change. Graphite automates this entirely.

When Stacked PRs Make Sense

When Stacked PRs Don't Make Sense

AI-Assisted Code Review

AI tools can catch common issues before human reviewers see them, saving reviewer time for higher-level feedback.

GitHub Copilot Code Review

# GitHub Copilot can review PRs automatically
# Enable in repository settings -> Copilot -> Code review
# It comments on PRs with suggestions for:
# - Bug risks
# - Performance issues
# - Security vulnerabilities
# - Code style improvements

AI Review Tools Comparison

Tool What It Does Integration Cost
GitHub Copilot Review AI comments on PRs GitHub native Part of Copilot subscription
CodeRabbit Detailed AI review comments GitHub/GitLab Free for open source
Sourcery Refactoring suggestions GitHub/GitLab Free tier available
Codium PR-Agent AI review bot GitHub/GitLab/Bitbucket Free (OSS)

What AI Review Is Good At

What AI Review Is Bad At

Use AI review as a first pass to catch low-hanging fruit, then have humans focus on design, architecture, and business logic.

Code Review Best Practices

For Authors

1. Keep PRs small. Under 400 lines of non-test code. If it's bigger, split it.

2. Write a good PR description. Explain why, not just what. Reviewers can see the diff -- they need context.

# BAD PR description
"Update user service"

# GOOD PR description
## Why
Users are seeing intermittent 500 errors when updating their profile.
The root cause is a race condition in the cache invalidation logic --
when two updates happen within the cache TTL, the second update reads
stale data and produces an invalid state.

## What
- Add optimistic locking to the user update path
- Invalidate cache synchronously instead of async
- Add a regression test that reproduces the race condition

## Testing
- Added test that runs 100 concurrent updates and verifies consistency
- Tested manually by rapidly clicking "Save" on the profile page

3. Self-review before requesting review. Go through the diff yourself. You'll catch obvious issues.

4. Respond to every comment. Even if it's just "Done" or "Good point, fixed." Don't leave reviewers wondering if you saw their feedback.

5. Annotate complex changes. Use PR comments to explain tricky sections:

// In the PR, add a comment on a complex section:
"This looks weird but it's necessary because the payment gateway
returns amounts in cents for USD but in base units for JPY (no cents).
See their docs: https://docs.stripe.com/currencies#zero-decimal"

For Reviewers

1. Review within 24 hours. Code review is the most common team bottleneck. Prioritize it.

2. Distinguish blocking from non-blocking feedback:

# Blocking (must fix before merge)
"Bug: This will throw a NullPointerException when `user.address` is null,
which happens for new users who haven't set an address yet."

# Non-blocking (suggestion, author decides)
"Nit: Consider renaming `data` to `userProfile` for clarity.
Not blocking."

# Question (need more context)
"Question: Why do we need this cache here? The database query
takes <5ms according to our metrics."

3. Review the design, not just the code. Ask:

4. Don't bikeshed. If two approaches are roughly equivalent, approve and move on. Save your review energy for things that matter.

5. Praise good code. "This is a clean abstraction" or "Nice test coverage" takes 5 seconds and makes authors want to keep writing good code.

Team-Level Practices

Rotate reviewers. Don't let the same person review everything. Spread knowledge across the team.

Set SLOs for review time. "All PRs get a first review within 4 business hours" is a reasonable target. Track it.

Review metrics that matter:

Metric Target Why
Time to first review < 4 hours Unblock authors quickly
Time to merge < 24 hours Prevent stale PRs
PR size < 400 lines Keep reviews meaningful
Review rounds < 3 Indicates clear communication
Reviewer coverage All team members Spread knowledge

Advanced: Merge Queues

Merge queues solve the "but it passed CI on my branch" problem. When multiple PRs merge around the same time, the combined result might break even if each PR passes individually.

GitHub Merge Queue

# Enable in repository settings -> Branch protection -> Merge queue

# Configuration options:
# - Required checks: Which CI checks must pass
# - Group size: How many PRs to batch together
# - Maximum wait time: How long to wait before running the queue

When enabled, clicking "Merge" adds the PR to the queue instead of merging immediately. The queue:

  1. Rebases the PR onto the latest main
  2. Runs CI on the rebased code
  3. If CI passes, merges
  4. If CI fails, removes the PR from the queue and notifies the author

Graphite Merge Queue

Graphite's merge queue is particularly good for stacked PRs -- it merges the entire stack atomically and handles the rebasing.

Review Checklist Templates

Create checklists for different types of changes:

<!-- .github/REVIEW_CHECKLIST.md -->

### General
- [ ] Code is readable and well-structured
- [ ] PR description explains the "why"
- [ ] No unnecessary changes included

### API Changes
- [ ] Backward compatible (or version bumped)
- [ ] Error responses follow our standard format
- [ ] Input validation is present
- [ ] Rate limiting considered

### Database Changes
- [ ] Migration is reversible
- [ ] Large table changes use batched updates
- [ ] Indexes added for new query patterns
- [ ] No N+1 query patterns introduced

### Security
- [ ] No hardcoded secrets
- [ ] Input is sanitized
- [ ] Authorization checks are present
- [ ] Sensitive data is not logged

### Testing
- [ ] Happy path covered
- [ ] Error cases covered
- [ ] Edge cases considered
- [ ] Tests are deterministic (no flakiness)

Measuring Code Review Effectiveness

Track these metrics to understand if your review process is healthy:

# Using GitHub CLI to gather PR metrics
# Average time from PR open to first review
gh pr list --state merged --limit 50 --json createdAt,reviews \
  --jq '.[] | {created: .createdAt, firstReview: .reviews[0].submittedAt}'

# Average PR size
gh pr list --state merged --limit 50 --json additions,deletions \
  --jq '.[] | .additions + .deletions' | awk '{sum+=$1} END {print "avg:", sum/NR}'

Healthy team metrics:

Summary

Code review is a skill that improves with deliberate practice and good tooling. Use GitHub's built-in features (templates, CODEOWNERS, branch protection) as a foundation. Add stacked PRs with Graphite when large features need to be broken down. Layer AI review tools to catch mechanical issues before humans review. Most importantly, invest in review culture: small PRs, fast turnaround, constructive feedback, and a shared commitment to code quality. The tools support the process, but the process is what makes code review valuable.