Antoine Lehurt

How to keep small PRs

Pull Requests (or PRs) are an essential part of team collaboration. It helps to catch issues, spread knowledge, and it brings confidence in our changes. When reviewing code, the reviewers also take responsibility for the changes that will be merged. So, when opening a PR, it’s important to share as much information as possible. It helps the reviewers to recreate the mental model we had when working on the task (Why we did that change? How we did it? Why we did it that way). The key is to keep our PRs small and focused. It’s fine to have a PR with several commits, and it’s even an excellent way to share the flow of our thoughts and changes. But the changes have the be related to the same goal. When PRs are significant, it’s harder to spot bugs, design flaws and the reviewers might feel overwhelmed. The worse case would be to approve the change without paying attention to it, which would defeat the initial purpose of working with PRs.

In this article, we will go through different scenarios to split a PR into smaller chunks.

Scenario 1: clean history, but too many unrelated changes

Before opening our PR, we realize that we could move some commits into a different PR. For instance, we created a new helper function on the way, and it will help reviewers to pay more attention to it.

$ git log
## branch: new-feature
* 8ee70d7 Commit 3
* b6a5da4 Add new helper
* d3b11d2 Commit 1

We will create a new branch from master and reuse the commit “Add new helper” using cherry-pick. And then create a PR for that new branch.

$ git log
## branch: new-feature

$ git checkout -b style-helper master
## branch: style-helper
$ git cherry-pick b6a5da4
$ git push origin

We can now go on GitHub and open the pull request.

The last step is to push new-feature to GitHub, but when we open the pull request, we need to change the base branch and use style-helper branch. (On GitLab it’s called the “target branch”.) We can check the commit history on the GitHub interface and see that “Add new helper” is not part of it. After merging style-helper PR, we should not forget to change the base branch to master before merging new-feature.

Scenario 2: need to split some commits

This time, we have mixed different changes in the same commit. We still want to create a different PR for our style helper, so we need to split that commit into multiple commits.

$ git log
## branch: new-feature
* 8ee70d7 Commit 2
* d3b11d2 Commit 1 <-- commit we want to split

The first step is to go back to “Commit 1” using rebase, and then we will reset that commit to split it using add --patch.

## branch: new-feature

git rebase -i d3b11d2^

Vim, or the editor defined in your gitconfig, will open and we specify which commit we want to edit.

pick 8ee70d7 Commit 2
e d3b11d2 Commit 1

We are now back at “Commit 1” and it’s now time to split it. We first need to reset the commit to put the change back in an unstaged state so we can later use add --patch.

$ git reset HEAD~
$ git add --patch

We follow the prompt and decide which hunk we want to stage using yes, no or edit.

$ git commit -m "Split 1"
$ git add --patch
$ git commit -m "Split 2: style helper"

We are now done with splitting the commit. We can complete the rebase.

$ git rebase --continue
$ git log
## branch: new-feature
* 8ee70d7 Commit 2
* 02dbeb3 Split 2: style helper
* cf4ca41 Split 1

We are at a similar stage as the first scenario, and we will go through the same steps for opening the two PRs.

Scenario 3: it’s a mess, let’s rewrite history

Sometimes things don’t go according to plan, and we end up with a spaghetti Git history. The best, in that case, is to rewrite it.

The first step is to find from which commit we want to start fresh.

$ git log
## branch: new-feature
* 255e8d5 Stuff 4
* 5c3fa7e Stuff 3
* deea3e8 Stuff 2
* 10a2b4a Stuff 1 <-- from here

$ git reset 10a2b4a^

Now everything is back to an unstaged state. We will commit the change we want to include in our first PR using add --patch. Then stash the rest to create a new branch and continue to commit from there and finally open our second PR.

## branch: new-feature

$ git add --patch
$ git commit -m "Commit 1"
$ git stash
$ git push origin

We can open the first PR using master as base branch.

Next, we create a new branch for the second PR.

## branch: new-feature

$ git checkout -b pr-2
## branch: pr-2
$ git stash pop
$ git add --patch
$ git commit -m "Commit 2"
$ git stash
$ git push origin

We can open the second PR using new-feature as base branch.

We repeat these steps until we don’t have anything left in the stage.

Conclusion

It might feel cumbersome to go through these steps, but I think it’s important to show empathy to the reviewers and put them in good condition.

These are the scenarios I have encountered to split a PR into smaller chunks, feel free to share a different way of doing.