Stacked Pull Requests: Keeping GitHub Diffs Small
Code reviews aren't just for catching bugs. They also help teams:
- Build trust and create a shared coding standard
- Force the discussion and vetting of code design
- Prevent broken windows
But if a pull request gets too big, it causes code review fatigue:
What you'll learn from this post:
- How to split a large PR into smaller PRs
- How to ship a dependent chain of PRs quickly
1. Create a WIP Pull Request
As with most Git workflows, start your work in a feature branch:
$ git checkout -b somefeature $ git commit -am "first change"
Now, before you've gone too far, open a WIP PR. Why?
- Give yourself an opportunity to get organized and come up with a plan
- Give the team an opportunity to see where you're going
- Be open; let people progressively review your progress
The following uses GitHub CLI tool hub, although you could just use the site directly:
$ hub pull-request WIP - Adding somefeature - [x] Do the thing - [ ] Do the other thing
Now hack away, committing early and often. Keep your team updated by pushing and checking off the PR to-dos.
Once the WIP is ready to ship, close the PR, we're ready to start our stacked PRs.
Tip: Don't delete the branch just yet. It's handy to have a remote backup just in case.
2. Create Stacked Pull Requests
Start by creating a new branch,
somefeature1, with all of the changes from
$ git checkout -b somefeature1 somefeature $ git reset master
git status now shows all the changes we made in
somefeature copied over to
somefeature1 in an uncommitted state. From here we can start creating the commits that will make up our stacked PR.
Reach for git add --patch to create our first logical commit in the
$ git add --patch
Once the changes have been staged, stash everything else and verify things still work:
$ git stash --include-untracked --keep-index $ make test ✓
Tests pass. Time to create our first commit and PR in the stack:
$ git commit -m "[somefeature1 - PART1] Make some change to x" $ git push origin somefeature1 $ hub pull-request [somefeature - PART1] Make some change to x
Commits 2 through N
For the next commit, start by creating a new branch,
somefeature2 with our stash popped:
$ git checkout -b somefeature2 $ git stash pop
Now back to
git add --patch to create and test our next commit:
$ git add --patch $ git stash --include-untracked --keep-index $ make test ✓ $ git commit -m "[somefeature - PART2] Make change to y" $ git push origin somefeature2
When creating the PR, make sure to base it off of the previous branch with the
$ hub pull-request -b somefeature1
Repeat this process until you've made it all the way through your stash.
The Finished Product
The result is a stack of focused, reviewable PRs.
Your Git graph should look a bit like so, with each branch depending on the previous one:
* somefeature3 / * somefeature2 / * somefeature1 / ------* master \ * somefeature
Your stacked PRs are now ready for review.
3. Feedback, Beautiful Feedback
Now that you've pinged the team for review, comments should start coming in.
Notice how much feedback stacked PRs provoke:
Compare that with a rather large PR that garnered very little feedback:
~60 comments difference—that's huge.
Now address feedback and push until you have enough stamps to merge.
Tip: Merge Up, Squash Down
When making additional commits to your branches, be sure to propagate them up the stack via
git merge, not
$ git co myfeature2 $ git commit -am "addressing this feedback" $ git commit -am "addressing that feedback" $ git push origin myfeature2 $ git co myfeature3 $ git merge myfeature2 # integrate changes from myfeature2 $ make test ✓
Rebasing focuses on keeping later branches tipped to the front of earlier branches. This is a noble goal, but it forces you to resolve merge conflicts 1 commit at a time! In the case of stacked PRs, it's just not necessary because we'll be squashing our commits when we merge the stack anyways.
Make your life easier and just use
git merge. If multiple commits have conflicts, you'll be able to resolve them in one go.
The same goes for staying up to date with
master: consider merging it in instead of rebasing.
4. Landing the Stack
At this point you've collected a "LGTM" for each PR in the stack, and it's time to merge.
It can be tempting to merge
PART1 of the stack in, since that PR will most likely be stamped first. Resist the urge; merging the first PR will cause all following PRs to become unmergeable! This is because GitHub does not support updating the target branch on PRs.
Instead, go through the stack from LAST to FIRST, and merge your stack into master:
The result is a single, well-reviewed commit on master:
5. Measure the Difference
Stacked PRs lead to more thoroughly reviewed code shipping faster.
My experience? Very positive. My last stack -
~2000 lines of code, over 60 comments of feedback, 20+ commits addressing feedback
Compare your next stack to a large and long running PR to see the difference.