Git Fixup Workflow

I usually do software programming considering a code review will happen right after, thus I strive to make my commits explain themselves logically and build up the context and knowledge required to understand what they are supposed to do. A storytelling in form of git commits. It helps reviewers, including my future self.
Sometimes, after coding a new feature in an elegant way with a nice sequence of commits telling the story in an easy way to review, I notice some typos, a missing comment here and there, a name that could be improved… Three comes the need to create small commits and a tedious rebase process. I assume readers are familiar with git rebase. It’s a powerful tool to rewrite parts of the commit history to make it clearer and more useful. To perform small fixups as the ones I described above, git rebase by itself becomes a little bit too complicated, because we need to:
create new commits with the intended fixes
run an interactive rebase to order the fixup commits right after the commits they must fix up
deal with potential merge conflicts
Here’s a small demonstration of how that would look like:

As you can see, it can be daunting to keep track of which commit fixes up which commit. Doing the wrong order easily leads to merge conflicts, scenario which I didn’t bother to demonstrate because that example is quite artificial and I’m too lazy to start delving into various scenarios. Can we make that experience a little better? Yes we can! Git offers a small automation for this process.
Welcome to the git fixup commits
git rebase has the --autosquash flag to help with this task. It automatically squash commits that contain
specially formatted messages into the commit they refer to. It also takes care of reordering the commits
during rebase so you don’t have to. If more than one of those special commits refer to the same parent, git
will apply both on top of that parent in the order they were created.
You don’t have to know how to write those special messages by hand. git commit has special variants that
help automating this task:
git commit --fixup=<COMMIT_SHA>With this variant you don’t write a commit message. It creates a new commit that will fixup the contents (but not the commit message) of the commit it refers too, that is, it will be squashed into that commit without asking for confirmation or revision.git commit --fixup:reword=<COMMIT_SHA>The opposite of the variant above, meaning it takes a commit message, which will replace the original commit message during rebase. It doesn’t change the contents of the commit, just the message. If you don’t supply the new commit message with the-moption, then you’ll have the editor open with the contents of the original commit message for you to edit as you see fit.git commit --fixup:amend=<COMMIT_SHA>It combines both behaviours described above, allowing you to rephrase the message and tweak the contents at once.git commit --squash=<COMMIT_SHA>It’s similar to thefixup:amendoption, meaning it allows you to change the contents and message of the parent commit we’ll squash into. The main difference is that with this option the commit messages are concatenated instead of this one replacing the parent.
When you finish all the small edits you wanted to do and feel ready to rebase, you have two options:
git rebase main --autosquash: will rebase your current branch on top ofmain, applying the fixups in the right order without asking you for further review or confirmation (unless a merge conflict shows up).git rebase main --interactive --autosquash: will show you the rebase TODO list already ordered based on your fixup commits before executing the rebase.
Here is a nice little demonstration of how this workflow may happen, starting from the same baseline as the previous example:

This workflow allows out-of-order fixups, that is, I can pick and choose the locations that I want to fix up and commit freely. The only decision that has to be taken carefully is which commit is the target for the fixup.
Better together with [n]vim
You might be thinking: "Oh yeah, this is nice, but it’s too much to type.". If you are a Vim or Neovim user, I got you covered. Tpope got you covered, better said. Tpope’s vim-fugitive plugin, the de facto standard git integration for vim and derivatives, has great support for those commands.
Assuming you made and staged some edits, when you navigate in a Fugitive buffer (like the GStatus or GLog buffers) and your cursor hovers a commit SHA you can use the following mappings to create fixup commits:
cf: will populate the command window with the command to create a fixup for the commit under the cursor, such as:Git commit --fixup:<THE_COMMIT_SHA_YOU_ARE_HOVERING>. If you hit enter, the fixup commit will be created. The contents must be already staged at this point.cF: does the above but also runs a non-interactive rebase-fixup with autosquash enabled when you hit enter, so your fixup is immediately applied to the history. Good when you don’t have anything else to tweak.cW: creates afixup:rewordcommit. Notice that even if you have staged contents, git won’t commit then, because this kind of commit only works with messages.cs: populates the command window with:Git commit --squash=<THE_COMMIT_SHA_YOU_ARE_HOVERING>. When you hit enter, a buffer for the squash-commit message will open to let you write the message you want to concatenate with the parent when rebasing.cS: similar to the above but also executes the non-interactive git rebase immediately. Good when you don’t have anything else to tweak.
To learn more about them type :help fugitive_c in your beloved editor.
Here’s a demonstration of how this works:

As before, when doing the rebase the commits are ordered without my intervention. I could, for example, skip
adding the -i flag to let git handle the rebase without showing the todo list or requiring my approval. If
you notice the bottom right corner of the screen, I didn’t type :G commit --fixup=… this time, just cf
and then <CR> to confirm the command that vim-fugitive placed for me in the command line. This makes the
workflow really productive.
As I said before, those examples are quite artificial, so I didn’t put enough effort to create examples of the other commit commands. I let as an exercise for readers that decide to try this workflow to play with the other options.
As of today I don’t know how to do this with VSCode. If you know similar actions or shortcuts for that editor, please let me know in the comments.
Bonus tip: ce
If you work often with the GStatus buffer opened and you often perform :Git commit --amend --no-edit, the
Fugitive mapping ce is for you. When you have staged contents and you’re in the GStatus buffer, hitting ce
will automatically amend the last commit with the contents of the staging area.
Conclusion
That’s it! I hope you find this post useful. I agree the best way of doing this is getting things right on the first run, but we are humans, imperfect and often make small mistakes. It’s much better to fix them this way and reserve the full blown interactive rebase for the more complex use cases it’s designed for.
Cheers!
This post is signed. To verify it’s signature, run the following commands on a Bash-like shell:
gpg --keyserver keyserver.ubuntu.com --recv-keys C8B952808BDB6325 POST_URL="https://cn.olivec.dev/blog/posts/git-fixup-workflow/" gpg --verify <(curl -sS "${POST_URL}index.html.asc") <(curl -sS "${POST_URL}")Those commands will import my public key into your gpg keyring and use it to verify the signature of this post.