Blog Post|#engineering

Stacked pull requests with squash merge

Cully Larson
Cully LarsonThursday, October 5, 2023
Stacked pull requests with squash merge

Stacked pull requests can be super useful. Even if you don’t know them by that name, you’ve probably used them (or wanted to use them). However, if you’re using stacked pull requests along with squash merge, there are some footguns you’ll want to avoid.

But first, what are stacked pull requests? A stacked PR is one that is based on another PR. Stacked PRs are useful if you ever open a PR and then want to work on another feature that uses code from the original PR, but you don't want to wait for a review/merge. You can branch off of the original PR and then, when you submit the new PR, base it on the original PR's branch. So let's say you do the following:

# start on your main branch git checkout main # create a new feature branch git checkout -b feature-a # make some changes git add -A git commit -m "feat: Feature A" git push # open your PR for feature-a # start another feature branch, based on feature-a git checkout -b feature-b # (notice we branched off of the feature-a branch) # make some changes git add -A git commit -m "feat: Feature B" git push # open a PR for feature-b. set the base branch in the PR to feature-a.

You’ll end up with a PR for feature-a, based on main, and another PR for feature-b, based on feature-a.

Once you’re at this point, and you plan to squash-merge your PRs, there are a few things to keep in mind:

1. Base your second PR on the first PR’s branch

When you create your second PR (for feature-b in our example) in Github, or whatever service you use, set the base branch to feature-a. If you set it to main, you’ll see all the changes from feature-a and feature-b in your diff.

2. Don't merge the second PR until the first PR is merged

Don't merge the second PR until the first PR is merged. If you do, it will merge into the first PR's branch (not main). Basically, your second PR will become a single commit on your first PR. This is almost certainly not what you want. Instead, mark the second PR as a "draft" until the first is merged to prevent it from being merged by mistake.

3. Squash merges don't preserve commit history

Squash merges don't preserve the commit history of your branch when it merges into main. It squashes all the commits into one commit on main. So once your feature-a branch is merged into main, git won't know that all those feature-a commits are now in main (just in one big commit). As a result, your feature-b PR will still have all those commits from feature-b in its commit history. Also, you won't necessarily be able to easily merge main into feature-b at this point.

To resolve this, if you want a clean commit history in your PRs, you'll have to do an interactive rebase and remove all of feature-a's commits from feature-b:

# backup your project folder before doing this git checkout main git pull git checkout feature-b git rebase -i main

WARNING: Before doing this, make sure that feature-b is up-to-date with feature-a (it has all of feature-a's commits). If you don’t do that, you’ll likely run into conflicts with your rebase. You should not have any conflicts during this rebase. If you do, it’s a sign that something was missed. Abort the rebase, resolve the issue causing the conflict, and try again. Don’t push forward with the rebase if there are conflicts.

4. There’s a process for pulling changes from main

While you have your stacked PRs open, if you want to pull some changes in from main, you should first merge main into feature-a and then merge feature-a into feature-b. Do not merge main into feature-b. Since feature-b is not based on main, it would mess up your git history and make your final merge of feature-b into main a pain.

To make this illustration more clear, let's say you have another feature that's based on feature-b. Let's call it feature-c. Your branch structure looks like this:

main -> feature-a -> feature-b -> feature-c

If you wanted to pull a change from main into these branches, you would:

# get the latest from main git checkout main git pull # merge main into feature-a git checkout feature-a git pull git merge main # merge feature-a into feature-b git checkout feature-b git pull git merge feature-a # merge feature-b into feature-c git checkout feature-c git pull git merge feature-b

Share this post


Interested in working with us?

Give us some details about your project, and our team will be in touch with how we can help.

Get in Touch