Skip to content

Stacked diffs: commits as review units

Treat each commit (or tiny branch) as the unit of review, not a large feature branch. Smaller reviews, less waiting, better commit hygiene — and a non-trivial rebase tax to pay.

Updated May 26, 2026

The default Git workflow says “one branch, one PR, one review.” Stacked diffs say “one commit (or very small branch) is the unit of review” — and your local work is a queue of dependent changes ahead of main, each reviewable independently while you keep building on top.

This is the workflow Meta popularised on Phabricator, Google practises in chained reviews, and modern tools (Graphite, ghstack, Sapling, spr) have brought to GitHub. It’s a force multiplier when it works. It’s also unforgiving when commit discipline isn’t there.

The mental shift

Branch-per-PR
main:    A---B---C
              \
feature:       D---E---F---G---H   (one big PR)

Reviewer reads 600 lines across 5 concerns. Author waits for review to start the next feature.

Stacked diffs
main:    A---B---C
              \
               D---E---F---G
               ^   ^   ^   ^
              PR1 PR2 PR3 PR4

Each commit is its own PR; PR 2 stacks on PR 1, PR 3 on PR 2, and so on. The author keeps coding on top while PR 1 is in review.

The shift isn’t about Git mechanics — it’s about engineering granularity. Each commit has one thesis, is independently understandable, and is small enough that review feedback arrives in hours, not days.

Why teams adopt it

Pattern
Stacked diffs
Problem

Branch-per-PR forces dependent work to either block on review (no parallelism) or pile into one large PR (no review signal). Large PRs are harder to review well, hide risk, and accumulate “address feedback” commits that erode history quality.

Solution

Split a larger initiative into a stack of small, dependent, reviewable commits. Publish each as its own PR. Keep coding on top while lower layers are in review. When feedback lands on a lower layer, rebase the stack to replay the upper layers on the updated base.

Consequences

Reviews are faster and higher-signal. Authors stay in flow. git log becomes useful: each commit is a coherent change, blame is meaningful, bisect actually works. The cost is paid in rebase frequency and tooling investment — without those, the stack becomes a tower of conflicts.

The author loop

  1. Start from updated `main`

    git fetch origin && git switch -c stack-base origin/main. The stack is built on a known base.

  2. Make one coherent change, commit, publish

    The commit has a thesis, a body, a test plan. Publish as a PR with whatever stack tool you use (gt submit, ghstack, spr diff).

  3. Keep building the next layer on top

    Don’t wait. Commit the next dependent change. Publish it; the tool creates PR 2 stacked on PR 1’s branch.

  4. When feedback arrives on a lower layer

    git rebase -i to edit the relevant commit. Git replays the upper layers automatically. Resolve conflicts at each layer. Re-publish.

  5. Land in dependency order

    PR 1 first, then PR 2, then PR 3. Most stack tools handle the rebase-after-land cycle automatically.

The feedback ripple

This is the operation that defines whether stacking feels good or painful:

git rebase --update-refs (Git 2.38+) updates dependent branch refs during a rebase, which removes a big chunk of the manual choreography for stacked-branch tooling.

The tooling landscape (2026)

ToolApproachWhere it lives
GraphiteCommercial CLI + web + VS Code. gt create, gt submit, gt modify, gt sync. Adds a merge queue and CI optimisation tuned for stacks.GitHub-native
ghstackOSS CLI. One PR per commit using synthetic branches. Land via ghstack land (not the GitHub merge button).GitHub-native
sprOSS CLI. Phabricator-flavoured. spr init, spr diff, spr land. Heavy emphasis on commit-as-review-unit.GitHub-native
SaplingSource-control system with stacks as a first-class concept. GitHub integration plus a stack-aware review UI.Alternative SCM
ReviewStackStack-aware review UI sitting on top of GitHub PRs.GitHub-native
GerritPatch-set review server. Commit-centric review long before “stacked diffs” was a term.Self-hosted

Pick one tool — mixing two stack CLIs on the same repo is a fast path to a confused team.

CI implications (don’t underestimate this)

Stacking multiplies PR count, which multiplies CI triggers. The interesting moves:

  • Selective CI by stack position. Run on the bottom and top of every stack; skip intermediates unless they touch CI-relevant files.
  • Dependency-aware test selection. Bazel/Buck/Turborepo-style — only run tests reachable from changed files.
  • Merge queue batching. Amortise validation across multiple stack items in one batch run.
  • Fail-open optimisation gates. If the CI optimiser fails to answer, default to running CI — never default to skipping.

Stacking improves throughput fastest when CI policy is adapted alongside the workflow. Without that, the workflow saves engineer time and burns CI budget at the same rate.

When stacking is the wrong choice

Stacked diffs assume a few things hold in your team. When they don’t, the workflow makes things worse, not better.

  • Changes are already small and independent. A team that ships 50-line PRs and gets review in an hour doesn’t have the problem stacking solves.
  • Compliance demands monolithic review artifacts. Some regulatory regimes want one signed-off artifact per change. Stacking fragments that audit trail.
  • The team can’t run force-push workflows safely. Stack maintenance is rebase + force-push. If branch protection forbids force-push on integration branches, the tool will fight you at every step.
  • The team lacks rebase fluency. This is the real adoption blocker. The first time the stack needs a rebase across three layers with conflicts, anyone who hasn’t internalised git rebase -i will abandon the workflow.

Anti-patterns

Anti-pattern
The mini-big-PR stack
Smell

A stack of 5 PRs, each 400 lines, all touching the same module.

Why it happens

The author adopted the workflow without internalising “one thesis per commit.” The stack is shaped like a single feature — just chopped into arbitrary slices.

Fix instead

The unit isn’t “a PR-sized chunk of the feature” — it’s one coherent change. Infra → API → UI is a stack. “Lines 1-200, lines 201-400, lines 401-600 of the same file” is not.

Anti-pattern
No commit discipline
Smell

Stack commits with messages like “wip”, “address feedback”, “fix tests” — interspersed with substantive commits.

Why it happens

The author treats the stack like a branch they’ll squash at the end. Stacked tools generally don’t squash — each commit becomes a permanent PR.

Fix instead

Each commit must carry the message it would have if it were the only commit in the PR. If a commit is genuinely a fixup, use git commit --fixup=<sha> and git rebase --autosquash to fold it into its target before publishing. See 0026 Three ways to squash, and when each is right · Git for the squash variants.

Adoption — what actually works

Pilot with one team that has high PR latency pain. Pick one tool. Cap initial stack depth at two layers while the team learns. Introduce a commit-message template (thesis, context, test plan). Add CI optimisation only once you have baseline metrics — otherwise you can’t tell whether the optimisation helped.

Four metrics to track during adoption:

  • Median PR size (lines changed). Should fall sharply.
  • Review turnaround (open → first review). Should fall.
  • Time-to-merge (open → land). Should fall, but watch for stalled lower layers.
  • Conflict/rework time. This is the canary. Rising rework time means the team isn’t keeping up with rebase hygiene — slow down before scaling.

Key takeaway

Stacked diffs are less about branch topology and more about engineering granularity plus review ergonomics. The biggest wins come when commit-level quality, stack-aware tooling, and CI strategy align. Without those three together, stacking is painful theatre. With them, it’s the difference between shipping at the speed of code review and shipping at the speed of code.

Related: 0030 Branching strategies: Git Flow, GitHub Flow, trunk-based · Git for how stacked diffs fit (or don’t) into common branching strategies.

Related