Or alternative title - How I learned to love Git rebase

When I first started working with Git, I was indoctrinated into the school of merge and was told to never rebase. Rebasing lets you re-write history. The whole point of Git is to track history. Therefore, rebasing is bad.

But there was one workflow that truly cemented my conversion and has become a regular part of my code writing process - rebasing on my active branch.

Even with linters and a spell checker extension installed in my code editor, from time to time, I’ll catch a typo I’ve committed to git. Or I’ll forget a change in a lingering file. And because the basic push workflow has become muscle memory at this point, I would push the commit before I noticed the mistake. I would fix it and do one of these…

$ git commit -m "fix typo"


But we can fix this quickly with an interactive rebase!

Fixup First

After fixing my mistakes, I’ll stage the file like normal. Then instead of -m and the cringy message, I mark the commit with the --fixup option. The command expects a commit SHA-1 to attach the fix to.

$ git add .
$ git commit --fixup 710f0f8

Another neat trick is to refer to the previous commit as the parent of the current one. HEAD~, HEAD^ will both work, as would HEAD~2 to refer to the commit before last, or the grandparent of the current commit. Note that ~ and ^ are not interchangable. Git is even smart enought to be able to find the first words of a commit message.

# these will all fixup your commit to the previous one.
$ git commit HEAD~
$ git commit HEAD^
$ git commit :/update

When I run git log, the history will look like this:

f7f3f6d (HEAD) fixup! update variable name
310154e update variable name
a5f4a0d (master, origin/master, origin/HEAD) working code on master

Let’s rebase!

We add -i to run the rebase in interactive mode and supply an argument of the parent of the last commit you want to edit. I use this as a rule: add 1 to the number of commits I need to go back. Adding --autosquash will pick up any commits prefaced with fixup! and set your interactive rebase session with the commands filled in.

$ git rebase -i --autosquash HEAD~3

The result of that command will be a list of your commits in ascending order (my default opens in vim) along with the action git should apply when running the commit. Note that the last commit already has fixup command attached.

pick a5f4a0d working code on master
pick 310154e update variable name
fixup f7f3f6d fixup! update variable name

# Rebase a5f4a0d..f7f3f6d onto a5f4a0d (3 commands)
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
# 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.
# Note that empty commits are commented out

At this point, if I’m doing a final clean up to push to a remote branch, I’ll maybe reword a commit message or maybe even squash some extraneous commits together. It’s worth having a read through the comments because this is a really powerful workflow, but fixup is the one I use the most.

Once you complete the rebase, your revised git log should have a new singular commit that has combined your fixup commit with the previous one. *bows and accepts applause*

2231360 update variable name
a5f4a0d (master, origin/master, origin/HEAD) working code on master

Autosquash magic

--autosquash will also pick up commits with --squash option, but I tend to not want to keep that message, so fixup works just fine for me. Squash might be a good option if you have a significant amount of new code but want only one atomic commit.

You can also set the following git config setting to omit having to include the autosquash option every time you run an interactive rebase.

$ git config --global rebase.autosquash true

My setups always have this set to true, which helped make fixups and squashing commits feel like second nature since to enter a rebase session, I only have to type git rebase -i HEAD~3 or however many commits I think I need to clean up.

And that’s how I converted to rebase!

Other helpful resources include this git tutorial on revising history. Once I digested that, I found it easier to understand the full doc on rebase.