DEV Community

Bella Woo
Bella Woo

Posted on • Originally published at bellawoo.com

How to Fix a Typo After You've Already Pushed Your Commit

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. 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.

Top comments (11)

Collapse
 
jdforsythe profile image
Jeremy Forsythe • Edited

Your alternative title is better. You should never rebase after you push to a remote. Ever.

Collapse
 
kiliman profile image
Kiliman

I would clarify that as never rebase after you push to a remote shared with others. I push and rebase my feature branch all the time. It's especially helpful when reviewing pull requests.

Collapse
 
bellawoo profile image
Bella Woo • Edited

A feature branch pushed up to a shared repository is available to others, so at this point, it becomes a matter of communication and etiquette. I wouldn't force push to a remote branch if someone else is actively committing or has an upstream based off of mine, but that situation is rare on the teams I work on.

Thread Thread
 
kiliman profile image
Kiliman

If we end up with a feature branch that other developers would work on (pretty rare), we would just treat it like we do for master. Create a new branch off it, rebase and merge. Same workflow.

Thread Thread
 
jdforsythe profile image
Jeremy Forsythe • Edited

While it may be rare, I prefer a workflow that doesn't have any gotchas. If we never rebase anything that has been pushed, our workflow never requires the developer to wonder if anyone else might be affected by a rebase. My devs have enough to think about. The more I can take off their plate, the better.

Thread Thread
 
kiliman profile image
Kiliman

That's the nice thing about git. It supports different workflows. So whatever works best for your team is what you should do. There is no right or wrong way.

Collapse
 
itsjuuulio profile image
Julio

As someone who makes many typos for a living, these are great!!

Collapse
 
aminarria profile image
Amin Arria

Really great of fixing multiple mistakes along the history.

One question, if it's just the latest commit you need to fix why not use git commit --amend?

Collapse
 
bellawoo profile image
Bella Woo

I often like to fixup and then keep going with my work. Then I'll rebase when I feel that I'm at a natural stopping point. But git commit --amend is an option!

Collapse
 
codefinity profile image
Manav Misra

😳 That’s a lotta work to fix a sepling error! Good for you, though. This level of attention to detail implies that you are a polished developer.

Collapse
 
bellawoo profile image
Bella Woo

Thanks! It's not only typos. Sometimes I may commit a "WIP", but fixup or squash the commit ass I get closer to opening a pull request. Getting comfortable with this technique has taught me the value of presenting polished work, to borrow your description. :)