DEV Community

Cover image for Move faster with Stacking PRs ๐Ÿš€
Gwion Robertson for InRange

Posted on • Edited on

Move faster with Stacking PRs ๐Ÿš€

Working with large pull requests can become difficult for both the original developer and the wider team. When working on a feature branch that's thousands of lines long, keeping track of all your changes and how they affect each other is no easy task. The same goes for the reviewer, it can be a nightmare sifting through large PRs, praying you don't miss the cause of the next prod outage.

Just split the large feature into smaller features, right? But what if multiple developers are working on the same large feature? If one devs work depends on another, must they wait for the other's PR to pass review before working on their task?

Stacking PRs seeks to resolve this problem, whilst also solving many more.

What is Stacking

Stacked Pull Requests (PRs), a.k.a. dependent, incremental or chained PRs, are PRs that branch off from other PRs.

This workflow can be used in your personal projects or with a team of developers to help move faster and with more conviction.

Benefits of Stacking

The idea behind stacking is to build small coherent units, in our case PRs, to make conceptualising these changes easier.

This helps the developer as well as the reviewer for a few reasons:

  • Atomising your changes like this results in simpler PRs, reducing the risk of the developer burning out
  • Simpler PRs also reduce review time, speeding up a Team's workflow
  • Less code means less rebase conflicts. Stacked PRs spend less time in the review process than 'traditional' PRs
  • If something goes wrong, and a revert is necessary, there are less cascading side effects because there's simply less code

It also helps multiple developers work code that depends on the same feature. The devs don't have to wait for a feature to be reviewed or merged, they can just stack on top of it.

ย Drawbacks of Stacking

There aren't many drawbacks to stacking; however, it is worth noting that the act of splitting up a large feature can be quite time-consuming.

Knowing when a feature is small enough to be a stack is sometimes difficult to decide. As you use Stacking PRs more, you begin to get a feel of what should be its own PR.

Another drawback is that it isn't widely adopted yet, and can be confusing to other team members if you use Stacking PRs and they do not.

Stacking Example

Let us consider a real-world example. An engineer, say Alice, is asked to migrate his company's React frontend to TypeScript. We can imagine this task requires 3 features:

  • Initialising TypeScript
    • Install TS with your preferred package manager
    • Configuration (tsconfig, linting etc.)
  • Migrate Components
  • Migrate Pages

Alice begins by creating a branch called ts-init from main. She commits her changes and creates a PR and requests a review.

stacking example #1

Now, Alice would like to begin migrating the components to TS. Traditionally, one might wait for the ts-init PR to be reviewed and merged to main before beginning a dependant feature, but with stacking, this is not the case.

Instead, Alice creates another branch called components-migration from ts-init. She commits her changes and creates a PR and requests a review as before.

stacking example #2

Here's where it gets interesting. Another engineer, say Bob, is assigned to migrate the pages. To accomplish this, he can simply create a branch pages-migration from ts-init, and both Alice and Bob work on their respective changes simultaneously.

stacking example #3

If, however, pages-migration was dependent on changes in components-migration, then we could inform Bob to work on something else whilst Alice finishes migrating the components. There is still the benefit that Bob could work on migrating the pages while components-migration and even ts-init are still in review.

stacking example #4

Finally, the engineers must safely merge these feature PRs with main. The order by which each branch is merged is solely dependent on the order of creation, that is to say ts-init must merge with main first, followed by components-migration and finally pages-migration.

stacking example #5

To reduce clutter, you might prefer this feature to read as one merge with main, then you could merge components-migration with ts-init, then components-pages and finally merge ts-init with main.

stacking example #6

How to stack an existing PR

There are a few different ways to convert an existing large PR into a stack of PRs, but I feel that this way is the easiest to conceptualise and track:

  1. Unstage previous commits so that you can reassign them to different PRs. You can achieve this by:
    • checkout the current PR's feature branch
    • soft reset main
    • restore all staged commits
$ git checkout large-feature-branch
$ git reset --soft main
$ git restore --staged .
Enter fullscreen mode Exit fullscreen mode
  1. You should rename the branch to indicate what your initial feature PR will be:
$ git branch -M initial-feature
Enter fullscreen mode Exit fullscreen mode
  1. Proceed to commit your initial changes and open up a new PR
  2. Create a branch from initial-feauture and repeat 3-4 until there are no changes left

You've now split up a large, review nightmare of a PR into small PRs that will benefit all involved!

ย Conclusion

Overall, stacked PRs are a great way to break up large features into concise, ingestible PRs that will make your life easier as well as your peers'.

The process can be daunting at first, but the benefits make it all worthwhile. Though there are tools available to help with Stacking your PRs (such as Graphite) which are full of great features, I would recommend giving Stacking a go without tooling first. This will help you get a good grasp of the concept and begin to understand where the benefits come from.

Top comments (0)