If you’ve ever opened a 2,000-line pull request and watched it sit for a week, you already understand the problem stacked diffs solve. Stacked diffs are a code-review pattern that splits one big change into a chain of small, dependent pull requests. Each PR in the stack is a focused, reviewable unit that builds on the one beneath it.

Originally borrowed from internal tooling at Meta and Google (where the pattern goes by names like stacks or Phabricator differentials), stacked diffs are now mainstream — popularised by Graphite, git-branchless, and now stkd, the open-source CLI this site is about.

The mental model

Picture a normal feature branch:

main
 └── feature/auth-everything

That single branch holds everything you needed to ship — database migrations, OAuth wiring, two-factor support, the recovery-code flow, the new UI. It’s a 4,000-line PR that touches 38 files. Reviewers stare at it, then quietly mute the notification.

Now picture the same work as a stack:

main
 └── feature/auth-base       PR #1: schema + base middleware
      └── feature/auth-oauth   PR #2: OAuth providers (depends on #1)
           └── feature/auth-2fa   PR #3: 2FA codes (depends on #2)
                └── feature/auth-recovery   PR #4: recovery flow (depends on #3)

Each PR is a few hundred lines. Each one builds on the previous. Reviewers can approve feature/auth-base today, even if feature/auth-recovery isn’t finished — and crucially, you can keep working on PR #4 while PR #1 is being reviewed.

That’s the whole pitch in one sentence: stop blocking on review by making each thing reviewable on its own.

The workflow, by example

Here’s what stacking a change looks like with stkd:

# Start a new stack on top of main
gt create feature/auth-base
# … make changes, commit normally with git …
git commit -am "schema + base middleware"

# Add a branch on top
gt create feature/auth-oauth
git commit -am "OAuth providers"

# And another
gt create feature/auth-2fa
git commit -am "2FA codes"

# Visualise it
gt log
# ● main
# └─ ● feature/auth-base
#    └─ ● feature/auth-oauth
#       └─ ◉ feature/auth-2fa   ← you are here

# Open PRs for the entire stack — one command
gt submit --stack
# ✓ Created PR #41 (feature/auth-base)
# ✓ Created PR #42 (feature/auth-oauth)
# ✓ Created PR #43 (feature/auth-2fa)

Three commits, three branches, three pull requests linked together — and you’ve never had to think about git rebase --onto once.

The magic moment: auto-restack

Stacks would be unbearable to manage by hand. The point of a stacked-diff tool is that when you change a branch in the middle, every branch above it rebases automatically.

Say a reviewer asks for a fix on PR #1:

gt down              # navigate to feature/auth-base
# … fix the thing, commit it …
git commit -am "address review"
gt restack           # rebases auth-oauth, auth-2fa, auth-recovery
gt submit --stack    # force-pushes updated branches, updates PRs

That’s it. Without a stacking tool you’d manually git rebase --onto four times, fix four conflict piles, and pray you didn’t lose a commit. With stkd you run two commands.

The “auto-restack” verb is the entire reason stacked-diff tools exist. If you remember nothing else from this article, remember that.

When the stack lands

Here’s the other satisfying moment: merging.

gt land --stack

stkd merges the bottommost PR (PR #1), waits for the provider to update the base branch, then merges PR #2, and so on. Branches that have already merged are detected and cleaned up automatically. Your local stack collapses cleanly:

main (now contains auth-base, auth-oauth, auth-2fa, auth-recovery)

No leftover branches. No git branch --merged | xargs git branch -d ritual. The DAG simply collapses.

Why teams actually adopt this

Read any case study of stacked diffs at scale (Meta, Google, Uber, Coinbase, plenty of others) and you’ll see the same four wins:

  1. Smaller PRs review faster. Studies consistently show review latency grows super-linearly with PR size. Five 200-line PRs ship in less wall-clock time than one 1,000-line PR.
  2. You stop blocking yourself. Instead of git stash and “I’ll start the next thing once this PR lands,” you just gt create and keep going. The reviewer’s clock and yours run in parallel.
  3. Reverts are surgical. If one piece of the stack has a problem, the bad PR can be reverted without touching the rest of the work. Compare that to ripping a single commit out of a 4,000-line monster.
  4. Code review actually happens. The biggest hidden cost of “let’s just open one big PR” is that nobody reviews it carefully — they skim, ✓ it, and move on. Small PRs invite real review.

When you shouldn’t stack

Stacking is overkill for one-off changes — bumping a dependency, fixing a typo, a single-file refactor. Use a normal branch for those. The break-even is roughly “this change will touch three or more loosely-related areas, or take more than a day to finish.”

It’s also less helpful when the work is genuinely atomic and can’t be split — a database migration that must land with its consumer code, for example. (Even then, stacks can help — base PR for the migration with a feature flag, follow-up PR to flip the flag.)

What about GitHub’s “Draft PRs” or GitLab’s “merge trains”?

Drafts and merge trains are useful but solve different problems. Drafts mark a PR as “not ready yet”; they don’t help split a 4,000-line change into pieces. Merge trains coordinate the order of merges; they don’t help authors structure their work.

Stacked diffs are about the authoring and reviewing flow. They compose with drafts (each PR in a stack can be a draft) and with merge trains (the stack lands one PR at a time, into the train).

The tools

You have options:

  • stkd — open-source, Apache-2.0, supports GitHub and GitLab (including self-hosted), MCP server for AI agents, optional self-hosted dashboard.
  • Graphite — the polished SaaS pioneer. Closed-source. GitHub-only. Free tier + paid tiers.
  • git-branchless — pure git-side, no provider integration.
  • Sapling — Meta’s open-source SCM with stacking built in; uses its own client instead of git.
  • ghstack — minimalist Python tool, GitHub-only.

If you want one command (gt), GitHub and GitLab support, true open source, and AI-agent integration, stkd is the option built for you. (Yes, that’s the pitch.)

A quick myth-bust

“Stacked diffs require force-pushing — they’re scary.”

Yes, the dependent branches get force-pushed when you restack. No, it’s not scary — every stacked-diff tool uses --force-with-lease (which refuses to overwrite work you don’t have locally), and the branches in a stack belong to one author by convention. You won’t clobber a teammate’s work.

“But our reviewers will get spammed with PRs.”

The opposite, actually. Each PR is smaller and reviews faster. The stack as a whole gets more attention than one giant PR would — but per-review the load is lighter.

“We use trunk-based development; stacks won’t fit.”

Stacks are the trunk-based development tool. The whole point is that nothing lives off-trunk for long — each PR in the stack lands as soon as it’s approved.

Where to next

If this resonates and you want to try the workflow:

  1. Install stkd (one command).
  2. Read Reviewing a stacked PR if you’re the reviewer.
  3. Read Migrating from Graphite if you’re already on the Graphite workflow.
  4. Hook your editor up to the MCP server so Claude or Cursor can drive stacks for you.

The shift takes about a day. After that you won’t go back to monster PRs.