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 for Git commits
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 using git commit
. 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.
How to create a git fixup
commit
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.
How to do git-autofixup
Let’s try this out. 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 about using git-autofixup
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.
We are really happy you read your way through the whole post. ๐ You clearly enjoyed this article, so please make sure you do not miss out on any of our upcoming posts by signing up for our newsletter. Feel free to share this article with your colleagues and friends.