Rebase For A More Concise Git Log

I saw a couple of tweets this week that reminded me of a small part of my workflow that has paid dividends in developer experience. I’m referring to git rebase; specifically, rebasing as a means of keeping the git log clean and concise.

The motivation for a clean commit log is multi-faceted, but boils down to “I want to be able to quickly read what has changed since the last time I pulled this branch, and I want to be able to easily roll-back changes even when I don’t understand the code.”

Rebase is only one part of achieving these goals, but it’s a big part. Whether it’s commits like “fix typo” or “make linter happy” (the latter being a commit message I wrote last week), these are just noise when you’re trying to understand why a recent deploy has your app consuming aws resources at a breakneck pace.

Let’s say you’re leveraging a “feature branching” workflow, where a developer creates a branch and does some work before merging that branch back to a shared branch like develop or master.

git checkout -b rad-new-feature
<do work>
git commit -am "lol idk"
<more work>
git commit -m "tell proguard to leave firebase alone and dont warn about it"
<more work>
git commit -m "adds firebase to app deps"
<more work>
git commit -m "make it build maybe?"
<more work>
git commit -m "forgot some stuff from the one where the gang adds deps"
<more work>
git commit -m "update gradle version"
git commit -m "integrate react-native-firebase per instructions"

If we were to merge this branch back into master, our commit log would look something like this:

$ git log --oneline
ff7c9da integrate react-native-firebase per instructions
413ef2a update gradle version
508a710 forgot some stuff from the one where the gang adds deps
823a514 make it build maybe?
39e8ac6 adds firebase and camera to app deps
1f160e7 tell proguard to leave firebase alone and dont warn about it
643ed94 lol idk
2a516a0 add Android google-services config
89c4e2a complete prototype for iOS

Ok, now that we’ve set the stage, here’s where rebase comes in! My teammates don’t need to see things like “lol idk”, so let’s clean that up a bit. We’re going to rebase “against master”, using an “interactive rebase”:

$ git rebase -i master

This will drop us into a vi session, where we can edit the commands that git will use to resolve these changes. I’m a big fan of squash and reword here. Let’s reword the latest message.

pick 643ed94 lol idk
pick 1f160e7 tell proguard to leave firebase alone and dont warn about it
pick 39e8ac6 adds firebase and camera to app deps
pick 823a514 make it build maybe?
pick 508a710 forgot some stuff from the one where the gang adds deps
pick 413ef2a update gradle version


# Rebase ff7c9da..413ef2a onto ff7c9da (6 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.

What we want to do is replace the word “pick” with the shorthand form of the “reword” command, which is simply “r”.

r 643ed94 lol idk
pick 1f160e7 tell proguard to leave firebase alone and dont warn about it
...

Write and quit the buffer, (press :wq), and you’ll get another editor instance where you can write a new commit message. Now the commit log shows:

$ git log --oneline
ff7c9da integrate react-native-firebase per instructions
6a112b7 update gradle version
89c801f forgot some stuff from the one where the gang adds deps
286cc1d make it build maybe?
96b5477 adds firebase and camera to app deps
3ccf5d3 tell proguard to leave firebase alone and dont warn about it
e65e7a2 A bunch of random stuff needed to make things build

Nice, now our coworkers won’t know that we’re just a big fraud who doesn’t know how gradle works. Let’s try to get some of these commits that are related into a single commit.

git rebase -i master
pick e65e7a2 A bunch of random stuff needed to make things build
pick 3ccf5d3 tell proguard to leave firebase alone and dont warn about it
pick 96b5477 adds firebase and camera to app deps
s 89c801f forgot some stuff from the one where the gang adds deps
pick 286cc1d make it build maybe?
pick 6a112b7 update gradle version
pick integrate react-native-firebase per instructions

Notice that the command for commit 89c801f is now “s”, which is “squash.” What we’re doing is combining that commit with the previous commit. Because the commands are executed top to bottom, that means the commit above 89c801f is the previous commit. Also notice that the order has changed. The reason for this, is that we want to squash 89c801f into a commit that is related to the changes it contains, and 96b5477 is that commit in this case.

This will give us an editor with both commit messages:

# This is a combination of 2 commits.
# This is the 1st commit message:

adds firebase and camera to app deps

# This is the commit message #2:

forgot some stuff from the one where the gang adds deps

Lookin’ good!

$ git log --oneline
ff7c9da integrate react-native-firebase per instructions
b85ab25 update gradle version
f72006f make it build maybe?
47663db adds firebase and camera to app deps
3ccf5d3 tell proguard to leave firebase alone and dont warn about it
e65e7a2 A bunch of random stuff needed to make things build

Let’s get this even leaner, using squash on multiple commits + reword and re-ordering.

pick 47663db adds firebase and camera to app deps
r f72006f make it build maybe?
s 3ccf5d3 tell proguard to leave firebase alone and dont warn about it
s e65e7a2 A bunch of random stuff needed to make things build
s b85ab25 update gradle version
pick integrate react-native-firebase per instructions
$ git log --oneline
ff7c9da integrate react-native-firebase per instructions
ec40ab3 proguard and gradle changes to build with firebase and camera dependencies
5d06bd9 adds firebase and camera to app deps

You could take it further, and combine those last 3 into a single commit. Using ‘f’, the shorthand for “fixup”, will meld the commit into the previous commit and drop that commits message.

pick integrate react-native-firebase per instructions
f 5d06bd9 adds firebase and camera to app deps
f ec40ab3 proguard and gradle changes to build with firebase and camera dependencies
$ git log --oneline
358c2e9 (HEAD -> rad-new-feature) integrate react-native-firebase per instructions
1360e9d (master) update splash screen
1360e9d update splash screen
89f4bd2 Maps on Android
b909729 import Marker directly
13eb007 rough Maps implementation
0c0eda9 add react-native-maps to package.json
21de33b iOS config for react-native-maps
106052b initial commit

Diffing against master via a Pull Request, or any other mechanism, will now show all of your changes combined into a single commit. As I said at the start, this small habit has had an effect on a lot of other areas of my workflow. Being able to easily and quickly grok how a codebase has changed over time and the ability to efficiently roll-back complex changes will have a profound effect on how your team can build and deliver software.

If you’ve given rebase a try, I’d love to hear your thoughts via twitter @mchandleraz. If you haven’t adopted it, I’d encourage you to give it a try.

-Matt

Published 12 Apr 2019

Programming Is Easy; Humans make it hard.
Matt Chandler on Twitter