Breaking a Long PR into Multiple Smaller PRs: A Practical Guide
Introduction and a bit of context
Recently, I was assigned a task to make some UI changes in our project. One of the subtasks was to update the z-index stacking context in our codebase. Basically, we were using too many variables for stacking contexts, and the logic could be simplified. Fairly simple, right?
The only thing is, this would mean making changes throughout the codebase. Any component using fixed z-index values (like z-50) would have to be updated (for example, with z-dialog, z-overlay). So, a lot of small changes in many files. This resulted in a total of 34 files changed.
Still, fairly manageable for someone to review my pull request. And this is where things started to get messy.
While I went on with my work to make the other UI fixes, I noticed the same problems in a lot of other files as well. And, instead of creating a separate branch off main and implementing the changes there, I made the changes in the same branch itself. A classic rookie mistake. This included moving and deleting quite a few files. Now, in fairness, I made this decision because the succeeding changes depended on the previous ones. While this reasoning made sense at the time, it ultimately made the PR harder to review—which is exactly what this post is about fixing!
When I was finally done with all my changes, I noticed the number of files changed: 74. And 977 lines added and 2814 lines removed!
The Problem
Usually, leads always advise juniors to raise smaller PRs because:
- It's simpler to review
- Easier to revert commits if there are merge conflicts or something needs to be changed/updated
- Smaller PRs get merged faster, reducing the chances of conflicts
Another rookie mistake I made was not making logical commits. Every task that you work on can be broken down into further sub-tasks. Each commit in the feature should ideally represent each sub-task so that later on it's easier to revert, or, as we'll see, easier to cherry-pick.
The Solution
The solution is simple: Break the pull request into smaller, logical, understandable PRs.
Each commit/sub-task I was talking about? We can create a new branch from main, and then group commits and push them into this branch. Again, we can create another branch from main, take the next group of commits, and then push them. Let's dig a little deeper.
I had 9 commits in the long-pr branch. Say, I figured out that commits #1, #2 need to be in a separate PR, #3, #4 in a separate PR, #5, #6, #7, #8 in a separate PR, and #9 in a separate PR.
Now the question is: will I create these 4 PRs with the base branch as main? What if commits #3, #4, which I decided to put in a separate branch, depended on the code changes introduced in commits #1 and #2?
Understand the problem? There are potentially two roads you can take:
Scenario 1: Independent Commits
If it so happens that your commits are independent of each other, then it's simple: you follow the above steps. Life is good. You create 4 separate branches, and raise PRs to the main branch.
Steps:
- Create branch
branch-1frommainand cherry-pick commits #1, #2 - Create branch
branch-2frommainand cherry-pick commits #3, #4 - Create branch
branch-3frommainand cherry-pick commits #5, #6, #7, #8 - Create branch
branch-4frommainand cherry-pick commit #9 - Raise all 4 PRs to
main- they can be merged in any order!
Scenario 2: Dependent Commits (My Case)
Since my commits were dependent on each other, I had two approaches:
Approach 1: Sequential Merging (Safer but Slower)
Create the first PR consisting of the first two commits. Tell my lead to merge it. Once they do that, GitHub will automatically update the diff to show only the changes not yet in main.
Steps:
- Create branch
branch-1frommain, cherry-pick commits #1, #2, and raise PR tomain - Wait for
branch-1to be merged intomain - Create branch
branch-2frommain(which now has commits #1, #2), cherry-pick commits #3, #4, and raise PR tomain - Wait for
branch-2to be merged - Continue this process until all commits are merged
Pro Tip: You can actually simplify this even further! Instead of creating new branches for every group of commits, use your original long-pr branch strategically:
- Create branch
branch-1frommain, cherry-pick commits #1, #2, raise PR and get it merged - Now, if you look at the
long-pr→maindiff on GitHub, it will automatically exclude commits #1, #2 and only show commits #3-#9! - Create branch
branch-2frommain, cherry-pick commits #3,#4, raise PR and get it merged - The
long-pr→maindiff now shows only commits #5-#9 - Create branch
branch-3frommain, cherry-pick commits #5, #6, #7, #8, raise PR and get it merged - The
long-pr→maindiff now shows only commit #9! -
Now you can directly merge the
long-prbranch tomain- no need to create a 4th branch! You can follow this approach until you or your lead feels the long-pr branch is small enough now to be reviewed on its own.
This works because GitHub is smart enough to recognize that commits #1-#8 (though still in your long-pr branch) are already in main and won't show them in the diff. Your long-pr branch isn't "long" anymore - it only contains the changes not yet in main!
Pros: Simple, less chance of mistakes, can reuse your original branch
Cons: You have to wait for each PR to be reviewed and merged before proceeding
Approach 2: Stacked PRs (Faster but Requires Careful Management)
You can create all branches upfront without waiting for your lead. This is known as stacking PRs. The advantage is that you don't have to wait - you can send them all your PRs at once for review.
Steps for Creating Stacked PRs:
- Create the first branch:
git checkout main
git checkout -b branch-1
git cherry-pick <commit-1> <commit-2>
git push origin branch-1
- Create the second branch from the first:
git checkout branch-1
git checkout -b branch-2
git cherry-pick <commit-3> <commit-4>
git push origin branch-2
- Create the third branch from the second:
git checkout branch-2
git checkout -b branch-3
git cherry-pick <commit-5> <commit-6> <commit-7> <commit-8>
git push origin branch-3
- Create the fourth branch from the third:
git checkout branch-3
git checkout -b branch-4
git cherry-pick <commit-9>
git push origin branch-4
Creating the Pull Requests:
- PR1:
branch-1→main - PR2:
branch-2→branch-1(base is branch-1, not main!) - PR3:
branch-3→branch-2(base is branch-2, not main!) - PR4:
branch-4→branch-3(base is branch-3, not main!)
This way, each PR only shows the diff for its specific commits, making them easy to review.
Merging Stacked PRs (CRITICAL STEPS):
Here's the correct way to merge stacked PRs:
First, get PR1 reviewed and merged into
main-
Then, for PR2:
- Go to PR2 on GitHub
- Click "Edit" next to the base branch
- Change the base from
branch-1tomain - GitHub will automatically update the diff to show only commits 3,4
- Get it reviewed and merge into
main
-
Then, for PR3:
- Change the base from
branch-2tomain - Get it reviewed and merge into
main
- Change the base from
-
Finally, for PR4:
- Change the base from
branch-3tomain - Get it reviewed and merge into
main
- Change the base from
Important: You must merge them in order. If you try to merge out of order, you'll get duplicate commits or conflicts.
Why this works:
- Each PR contains only its relevant changes when reviewed
- By changing the base branch after each merge, GitHub automatically removes the already-merged commits from the diff
- All changes eventually land in main separately, maintaining clean history
- Your team can review all PRs in parallel, but they merge sequentially
Alternative Stacked PR Approach (All from Main)
Some teams prefer a slightly different approach:
-
Create all branches from
main:-
pr1frommain(commits #1, #2) -
pr2frommain(commits #1, #2, #3, #4) -
pr3frommain(commits #1, #2, #3, #4,#5, #6, #7, #8) -
pr4frommain(commits #1, #2, #3, #4, #5, #6, #7, #8, #9)
-
Raise all PRs to
mainMerge them sequentially: pr1→main, then pr2→main, etc.
This approach is simpler but requires more rebasing as you go.
Pro Tips
Communicate with your team: Let your reviewers know these PRs are stacked and should be merged in order.
Keep the stack shallow: Try to limit stacks to 3-4 PRs max. Beyond that, it gets hard to manage.
Make logical commits from the start: This makes splitting PRs much easier. Each commit should represent one logical change.
Conclusion
Breaking a large PR into smaller ones might seem like extra work, but it pays dividends in:
- Faster review cycles
- Easier debugging when something goes wrong
- Better collaboration with your team
- Cleaner git history
The next time you find yourself with a 74-file PR, remember: it's never too late to split it up. Your reviewers (and your future self) will thank you!
Have you dealt with massive PRs before? What strategies worked for you? Let me know in the comments!
Top comments (0)