Keeping your Git commit history straight

Eddie Prislac
The SitRep
Published in
4 min readJan 21, 2019
Image stolen from here: https://tugberkugurlu.blob.core.windows.net/bloggyimages/d773c1fe-4db8-4d2f-a994-c60f3f8cb6f0.png

I’ve been using Git professionally and as my personal version control system of choice for a while now, but I have to admit that for a long time I was ignorant as to what most folks were expecting out of a git history. On many of the projects I’d worked on, it common to submit a branch with a ton of commits for peer review. This would then get merged, including a merge commit and all the commits made in the branch. We then started squashing our commits when merging, but this would leave a nasty, uninformative merge commit, and nothing else… essentially robbing the dev who’d actually worked on the branch of any credit/blame because the admin who’d performed the merge would be listed as its author (You can see some examples of those nasty merge commit messages in the image above). Then something happened that just sort of made the importance of a clean commit history click for me. I’d just written a runner for Pronto, and wanted to get my runner listed on their README so that others could take advantage of it, so I submitted a pull-request on which I’d committed several edits, until I was sure I’d gotten the wording right. Upon review, I was told that I needed to clean up the Git history in my branch, before it would be accepted.

“Clean it up? How would I do that?”, was what I thought to myself, proving my own ignorance at the time. I didn’t want to seem ignorant, so I did some digging, and eventually settled on doing a soft reset to the base commit of my branch like so:

git reset --soft <commit sha of the base commit>

I then added the rest of my changes with git add -A, committed the results, then force-pushed them up to my branch with git push -f, to rewrite the commit history. While this worked at the time, and would be my M.O. for revising my history for a good long while, I eventually learned of its shortcomings… namely, it forces you to write up a new commit message, and even copy/pasting this can be a chore. It also treats all the changes as if they were one change, as really, they are now… Not great if you’ve made a mistake, and reset to the wrong commit. This is where I’ve found git commit --fixup and git rebase -i --autosquash to be useful.

The “fixup” option is used for when you’ve already made a commit to your branch, but for whatever reason, you need to go back in and make edits. This will happen most frequently in the peer review process, but will also likely happen in the course of your day-to-day work. The usage is below:

git commit --fixup <git sha we're updating>

What this does is tells git that this commit is building on work done in the previous commit. You can repeat this process as many times as necessary, with each new commit being a fixup of the previous commit SHA. (Note, this SHA does NOT need to be immediately prior to the previous commit). Alternatively, (and some would say, recommended), instead of the SHA, you can use the commit title (the reason for doing so is sound, as you may need to do multiple passes with rebase, and if you use the SHA, you’ll lose the reference to the previous commit). The git message it includes is sparse, and there’s a reason for this, as it’s not really meant to be permanently left in place. “Fixup” really shines when used in conjunction with git rebase -i --autosquash (usage below):

git rebase -i --autosquash <base commit SHA>

The base commit SHA in the above example is the commit we’re rebasing our work on… in most cases, this is going to be the base commit of your branch… that is, the last commit prior to all the ones you’re going to be squashing together. What —-autosquash does is takes all those commits, reorders them based on which “fixup” commits are pointed at which base commits, squashes them all down, and uses the commit message from the first commit after your base. The -i flag opens up a commit message editor prior to committing the rebase which will show you the commits you’re including, in the order that they’re being applied, you’ll see your base commits listed as “picks”, with each “fixup” being applied after its base (This message is not used in the commit, it just shows you the order… when you save it, the commit message for the first commit will be applied. If you exit out of the rebase with a blank commit message, say, if you made a mistake, your rebase will be cancelled, allowing you to try again). Afterwards, if you’re like me and you included a change-list in your commit message, you’ll want to go back into it with git commit --amend and update the change-list, if any updates were made to files not previously included. Finally, git push -f to force-push your change and overwrite your origin branch history.

Using this process, I’ve been able to keep the commit history for each of my branches down to one commit, in most cases, allowing me to use the “rebase and merge” feature on GitHub when merging. This basically forgoes the usual, ugly and uninformative merge commit, and instead makes it look as if I’ve committed my work directly to the branch I just merged to. This way, if, God forbid, anything I’ve committed needs to be reverted for some reason, there’s only one commit to worry about, making it a lot less of a hassle for all those involved.

References:

--

--

Eddie Prislac
The SitRep

Devil-Dog, Code Warrior, Fevered American Super-Mind. Eddie Prislac is a 12yr+ software development veteran, and Head FNG Wrangler at Vets Who Code.