When working on a team, one of the most inherently difficult and more involved processes is code reviews. To review a large pull request (PR), you need time and context as well as the energy to parse and hold your mental boundaries in focus.
The same cannot be said for small PRs, however, where it’s much easier to consider changes and to suggest changes of our own. So how can we introduce large changes while avoiding cognitively overloading our teammates? This is where stacked pull requests come into play.
In this article, we will cover what stacked pull requests are, when and how to use them, and how to convert a monolithic PR into a stacked one.
What are stacked pull requests?
Stacked PRs, also know as dependent, incremental, or chained PRs, are pull requests that branch off from other pull requests. In
git terms, they are feature branches that are checked out from other feature branches to build small and coherent units to represent changes.
When working with stacked PRs, it’s helpful to think of your branches as a similar layer of code-change organization to git commits. You have the choice between pushing all your changes as a single big commit and organizing your code in separate commits. Having multiple commits is the better practice. So, what good is there in doing the same with your branches?
When and why should you use stacked PRs?
- To split large PRs
- To share a piece of code between multiple feature branches
- To make it easy for two people to work on the same feature
As previously stated, stacked PRs are useful when wanting to split large pull requests. The other situation where stacked PRs really shine is when you want to use a particular change in two or more branches.
For instance, imagine wanting to migrate a codebase to TypeScript where you rewrite the pages in TS while your teammate rewrites the components. The TypeScript setup (dependency installation,
tsconfig.json, etc.) would have to be shared between the two of you, either by eagerly committing the setup to the
develop) branch, or by stacking your PRs on top of a
ts-setup feature branch.
This would allow the two branches,
migrate-components, to share the TypeScript configuration in a
master-like relationship with the
ts-setup branch. This means that if a change occurs in
migrate-pages would have to merge or rebase
If page migration is dependent on components migration, you can stack the branches even further.
This is especially handy when two people are trying to collaborate on the same feature. Stacking two branches is easier to handle than working on the same branch.
How to stack pull requests
To stack two PRs, checkout the first branch from your base
develop) and push your changes.
$ git status # we are on master On branch master $ git checkout -b ts-setup # checkout from master $ npm i -D typescript && npx tsc --init $ git add . && git commit -m 'Setup TypeScript' $ git push -u origin ts-setup
In your GitHub repository, you’ll be prompted to create a pull request from
Create the PR with the base as
Then, checkout the second branch from the first.
$ git status On branch ts-setup $ git checkout -b migrate-components # checkout from ts-setup $ mv components/Button.jsx components/Button.tsx $ git add . && git commit -m 'Migrate button to TS' $ git push -u origin migrate-components
This effectively turns
migrate-components into stacked branches ready to become stacked PRs.
Notice that while
master is set as the base of our PR, the changes from
ts-setup (“Setup TypeScript” commit) are present, and our commit count is at two.
Changing the base branch to
ts-setup removes overlapping commits, bringing our commit count to just one.
Make sure to clearly state that a PR is stacked on top of another. A label might also help.
The worst-case scenario is someone merging a PR, pulling
master, not finding the changes, and getting confused, which begs the question, how do you merge stacked PRs?
How to merge stacked pull request
Squash, merge, or rebase?
The only restriction you have on merging while working with stacked PRs is that you cannot “squash and merge” or “rebase and merge.” You must merge directly. This restriction does not apply to the last PR on a given PR chain.
This is due to how git’s history works. Git tracks changes through commits by commit hashes. If you recall, changing the base from
ts-setup shaved off the common commit between
Git knew to do so because it saw a commit with the same metadata on the two branches. Squashing and rebasing both overwrites Git’s history (albeit in different ways), removing the overlap that deemed the two branches continuous.
In what order should I merge?
TL;DR: All orders are valid. It depends on how you want the merge commits to look on
The order in which we should merge or stacked PRs is completely subjective. If we merge
ts-setup with a commit message of “Setup TypeScript” and delete the PR branch, GitHub will automatically pick up on this and change the base of our
migrate-components PR to
This will give us the chance to merge with master with a separate merge commit message, “Migrate Components to TS.”
Alternatively, we can first merge
ts-setup, then merge
master with a single merge commit message to
master of “Setup and migrate components to TS.”
Splitting an existing large PR into a stack of PRs
Let’s say we’re trying to merge a large
migrate-to-firebase branch with
develop. The PR affects tens of files and has proven to be difficult to review. To split it into multiple PRs, locally, we do the following:
$ git checkout migrate-to-firebase $ git reset --soft develop $ git restore --staged .
First, we checkout the branch and then we uncommit all the changes that do not exist on
develop without removing the changes themselves. This results in all the changes being staged as
git status would indicate, so we unstage them by running
git restore --staged.
You can rename the branch to give an accurate account of what it actually does by running:
$ git branch -M setup-firebase
You then can start adding, committing, and checking out new branches, forming a chain.
Using stacked PRs in Gitflow
One of the issues you encounter while using the Gitflow workflow is being unable to selectively push feature branches from
develop in a given release. For example, if you have a redesign coming up that you wish to work on but not release yet, you can scope that redesign in a parent feature branch that smaller feature branches can be stacked on top of, then merge that parent branch with
develop once it’s finished.
In this article, we learned about stacked PRs, why and when to use them, and how to create and merge them. We also talked about how they can enhance the Gitflow workflow. Thanks for reading!
LogRocket: Full visibility into your web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.