How to Fix a Typo After You've Already Pushed Your Commit
Tags: git, tutorial
Published: 2020-02-07 16:33:05

### 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"```

Gross.

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](https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#_ancestry_references). 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](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History). Once I digested that, I found it easier to understand the [full doc on rebase](https://git-scm.com/docs/git-rebase).