DEV Community

Tom Mango
Tom Mango

Posted on • Originally published at sleepingpotato.com

Stacking PRs & Squashing Merges

Sometimes you’re working on a feature that’s too big for one PR and splitting it up makes it easier to review. The way I handle this is by creating a first feature branch, then branch second off of it, then third off of second. As each branch is ready for review, I open a PR and each subsequent PR stacks on the last, so reviewers can focus on one layer at a time.

That setup works really well, but can get messy when your team uses a --squash merge strategy where each PR turns into a single commit when merged into main.

Once you squash-merge first into main, all of those commits are replaced with a single commit on main. This is great for historical context and readability of commit history in your main branch, but your next PRs (second and third) are still branched off the old history, so they’ll suddenly show all of first’s commits too. The diffs get noisy and reviewers can’t tell what’s actually new.

Here’s how to fix that without recreating branches or PRs.

Step 1: Rebase the next branch (second) onto main

This tells Git: “replay just the commits unique to this branch on top of main, and drop everything from first.”

    git fetch origin
    git checkout feature/second
    git rebase --onto origin/main origin/feature/first
    git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

Then update the second PR’s target branch to main. Now you'll have a clean second branch and PR with just its changes.

Step 2: Rebase deeper branches (third) with --fork-point

Once you’ve rebased second, its history is now different. If third was branched off that earlier version, it’ll now include all of first and second’s commits. The fix is to tell Git find where it originally forked during the rebase:

    git fetch origin
    git checkout feature/third
    git rebase --onto origin/main --fork-point origin/feature/second
    git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

--fork-point finds the right spot where third branched from the old second, even if the commit hashes changed during the rebase.

Why this works

A squash-merge creates a brand new commit on main.

Rebasing with --onto (or --fork-point) replays your branch’s commits on top of that new base, dropping any that already exist upstream.

You end up with:

  • main → clean history, each squash commit representing a full PR.
  • stacked branches → rebased to show only what’s new from that branch.

TL;DR

If you’re stacking PRs, every time you squash-merge one into main, rebase the next one with:

    git rebase --onto origin/main --fork-point origin/<previous-branch>
Enter fullscreen mode Exit fullscreen mode

Then push --force-with-lease and retarget the PR to main.

It’s a small bit of cleanup that keeps each PR focused, readable, and mergeable.

Top comments (0)