If there's one thing I can say about code review, it's that everyone seems to hate it. If you're the one waiting on a review, it's frustrating to wait. If you're the one who has to review the code, taking the time out from other work to do it is annoying. Code review is long and tedious, and is all too easy to procrastinate.
But what if there were a way to speed up the process and make it less boring? To help with this, there are two things I like to use: fixup commits, and interactive rebasing.
Let's start by having a look at fixup commits
What is a Fixup Commit?
Let's imagine you have a fairly chunky commit and someone reviews it and then recommends a change. Once you've made a change, they don't need to review the whole thing again. They only need to look at the changed code. This is where we would use a fixup commit.
A fixup commit is essentially a commit whose message starts with !fixup
. The rest of the commit message matches another of your commits. So, it is a really clean way to separate your changed code from the rest of the commit, whilst also making it clear to the reviewer which commit it belongs to.
Commit 3abc
!fixup A commit to add some functionality
Commit 2abc
A commit to add some styling
Commit 1abc
A commit to add some functionality
In this above example, it is clear that commit 3abc
belongs to 1abc
because the message after !fixup
matches. Yet it still means a reviewer doesn't have to look at 1abc
again.
How do I Create a Fixup Commit?
To create a fixup commit, there are two things we need:
- Your changes, staged but not yet committed
- The ID of the commit you want to fixup
In the above example, you'll notice that the most recent commit wasn't the one that was fixed up. Often, you won't want to just fixup the most recent commit. So you will need to figure out which of your commits your new change is "fixing" (hence the name "fixup").
Once you have both of these, there is a single simple command you can run in the terminal:
git commit --fixup <ID of commit you want to fix up>
So for example, let's go back to our example above. Our fixup commit was named !fixup A commit to add some functionality
. In order to create this, we would run (using 1abc
as the commit ID as it's the one we want to fix up):
git commit --fixup 1abc
What is Interactive Rebasing?
Fixup commits are incredibly helpful for code review, but we probably don't want a bunch of commits beginning with !fixup
to be merged into the main branch. It will look messsy and it's not necessary for the git diff to know whether a small change was a fixup or not. This is where interactive rebasing comes in. This is a way we can "squash" our fixup commits into the main commits that they are fixing up.
How do I do an Interactive Rebase?
To do an interactive rebase, there are two things we need:
- At least one fixup commit
- The ID of the commit before the one you're fixing up
Once again, it's a simple single command we need to run to perform an interactive rebase:
git rebase -i --autosquash <ID of commit before the one you're fixing up>
Once this has run successfully, your fixup commit(s) will have disappeared as these changes will have been squashed into the original commits. Note that after running the above command, you may need to fix some merge conflicts, especially if you have multiple fixup commits and/or you're fixing up a commit that has a bunch of others that come after it.
How About an Example of the Whole Thing?
Let's have a look at these two (imaginery) commits similar to ones from before:
Commit 2abc
A commit to add some styling
Commit 1abc
A commit to add some functionality
This time, let's imagine we have some changes we want to squash into the second commit, after doing a fixup commit. First, we can do a fixup
commit:
git commit --fixup 2abc
This gives us the following in our commit history:
Commit 3abc
!fixup A commit to add some styling
Commit 2abc
A commit to add some styling
Commit 1abc
A commit to add some functionality
Once the reviewer has had a look at the fixup commit and is happy with it, we can squash the fixup commit with an interactive rebase like so:
git rebase -i --autosquash 1abc
This leaves us with the following in our commit history. Although commit 3abc
appears to be gone, its changes aren't, as they're now part of the commit it fixed up:
Commit 2abc
A commit to add some styling
Commit 1abc
A commit to add some functionality