Technical | 2021-03-04

Effortlessly correct your Git commits with git-autofixup

Sit back and relax as git-autofixup rewrites your commit history.

The version control system Git gives us powerful tools to orchestrate collaborative software development. Developers use code reviews to catch many issues long before a customer would notice them. To address issues that are found during a review, a developer typically has to make numerous small changes. This can be a tedious process, involving lots of busywork.

To address review comments, you often want to modify earlier commits that were not quite right. This article is a tutorial on how to do that by automatically creating fixup commits using git-autofixup. As a result, you can focus on more interesting problems.

A Hands-on Example

Let’s say you want to share a music diary with your friends. Every day, you and your friends will add your favorite songs to this file. To not accidentally overwrite each other’s additions, you store the diary in a Git repository.

To get started, create a fresh Git repository and add an empty music diary, with a heading for each day of the week.

$ git init my-favorite-things && cd my-favorite-things
Initialized empty Git repository in ~/my-favorite-things/.git/

$ echo > songs.md "\
# Our Music Diary

## Monday

## Tuesday

## Wednesday

## Thursday

## Friday"

$ git add songs.md
$ git commit -m "Initial commit"

Since you don’t want to send out changes every single time you add a song, you use a branch-based workflow: your changes live on a local branch until you merge them to the shared remote branch. Let’s create a new branch for the songs we add this week:

$ git checkout -b my-songs-of-the-week
Switched to a new branch 'my-songs-of-the-week'

Great, now you have your own branch where you can add songs as you like. At the end of the week you can share your additions.

Let’s add a song for Monday, and commit that change. Here we use the sed text editor to first search for our Monday line, and then use the sed’s “a” command to add a new item to the list.

$ sed -i "/Monday/a- Overjoyed" songs.md
$ git commit -am "Monday"

Brilliant! Now do the same for the rest of the week:

sed -i "/Tuesday/a- Isn't he lovely" songs.md
git commit -am "Tuesday"

sed -i "/Wednesday/a- Superstition" songs.md
git commit -am "Wednesday"

sed -i "/Thursday/a- Sir Duke" songs.md
git commit -am "Thursday"

sed -i "/Friday/a- Cause We've Ended as Covers" songs.md
git commit -am "Friday"

This is our diary on Friday:

$ cat songs.md
# Our Music Diary

## Monday
- Overjoyed

## Tuesday
- Isn't he lovely

## Wednesday
- Superstition

## Thursday
- Sir Duke

## Friday
- Cause We've Ended as Covers

Looks great! But wait, did a typo just sneak in? If you’re a fan of Stevie Wonder, you will have noticed that two song titles are misspelled! Let’s edit the diary once again, to correct these mistakes. You can use sed’s “s” command to replace each occurrence of a string.

sed -i "s/Isn't he lovely/Isn't she lovely/" songs.md
sed -i "s/Covers/Lovers/" songs.md

We could create a new commit with these corrections, but that would overthrow the nice structure of having one commit per day. Also, you want to share your music with your friends, but not your silly mistakes, right?

Luckily, you have read a tutorial on git rebase so you know how to change your commit history: you just need to create a so-called fixup commit that fixes a typo and then use git rebase -i --autosquash to modify the commit that introduced the typo.

To create a fixup commit, we need to know the target commit to modify. You have made two fixes that belong to different target commits - but which ones? A simple heuristic is to use a commit that added a line that the fix corrected. Let’s ask git blame which commit added the faulty line. Since Isn't he lovely was on line number 7, you can pass an inclusive range of only that line to git blame.

$ git blame -L 7,7 HEAD songs.md
91492e3c (Johannes 2020-10-16 09:44:06 +0200 7) - Isn't he lovely

Gotcha! The commit with ID 91492e3c needs to be corrected. Let’s do that. Remember that you only want to change one line in this commit, so you can use the --patch option to select the right one.

$ git commit --patch --fixup 91492e3c
diff --git a/songs.md b/songs.md
index 8795f4b..6229282 100644
--- a/songs.md
+++ b/songs.md
@@ -4,7 +4,7 @@
 - Overjoyed
 ## Tuesday
-- Isn't he lovely
+- Isn't she lovely
 ## Wednesday
 - Superstition
(1/2) Stage this hunk [y,n]? Yes, please!
@@ -13,5 +13,5 @@
 - Sir Duke
 ## Friday
-- Cause We've Ended as Covers
+- Cause We've Ended as Lovers
(2/2) Stage this hunk [y,n]? No thanks, leave this for later ;)
[my-songs-of-the-week 84460d1] fixup! Tuesday
 1 file changed, 1 insertion(+), 1 deletion(-)

Ok, that was easy! Now you just need to do the same thing again for the other mistake. Wait a minute… 🤨 “the same thing again”, huh? Well, if it’s so easy, why can’t the computer do that for me? 🤔

Turns out Jordan Torbiak’s git-autofixup does exactly what we want: it runs git blame on each change and creates fixup commits, just like we did.

Let’s try that. First undo the fixup commit you just created, but leave the diary as-is:

$ git reset HEAD~
Unstaged changes after reset:
M	songs.md

Then just run git-autofixup. Passing the special commit reference @{upstream} tells it to consider all commits on the current branch as desirable fixup targets.

$ git autofixup @{upstream}
[my-songs-of-the-week 88d2457] fixup! Tuesday
 1 file changed, 1 insertion(+), 1 deletion(-)
[my-songs-of-the-week aadb1e5] fixup! Friday
 1 file changed, 1 insertion(+), 1 deletion(-)

Observe that git-autofixup created two fixup commits for the errors made on Tuesday and Friday.

Finally, run git rebase -i --autosquash, and you’ll have an immaculate history, ready to be shared. With the first approach, we had to figure out the correct arguments for git blame and git commit, whereas git-autofixup is smart enough to do the same out of the box.

Side note: git revise nicely complements git-autofixup. It is an efficient alternative to git rebase which avoids invalidating builds by rewriting commits without touching worktree files.

Takeaways

At Symflower we value a thorough review process, and git-autofixup saves us a lot of time implementing that. We hope you find it useful, too. Keep in mind that fixup targets are assigned using a heuristic; when in doubt please double-check that your fixup commits are correct. 😉 If you want to learn about the technical details of how git-autofixup works, the original blog post is a good place to start.

If you love working on challenging problems, using tools that make development fun, and maybe even want to help us enhance our workflow, consider joining us!.