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
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-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
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
# 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
While you have your stacked PRs open, if you want to pull some changes in from
main, you should first merge
feature-a and then merge
feature-b. Do not merge
feature-b is not based on
main, it would mess up your git history and make your final merge of
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