Git gives us a simple but effective way to retroactively correct small oversights with the fixup option, so they’re no longer noticeable at all. Let’s explore how exactly this works in this article.
A Clean History
The fixup workflow primarily serves to keep the Git history clean. A clean history contributes greatly to understanding why the code you’re currently working with looks the way it does. But it also allows you to quickly pull small fixes into another branch when you need to.
It would be awkward if, instead of one commit, we had to cherry-pick four or five different commits at the end — worst case, not even related ones — because two subsequent commits fix typos and two more fix bugs, even though everything originated in the same branch.
Git therefore allows us very easily to make such corrections retroactively when committing with the --fixup option, and then with a simple additional step — the interactive rebase with the --autosquash option — to move these corrections to the right place in the history. This lets us very elegantly achieve a clean history that significantly simplifies future work for our team and ourselves.
The Oversight
Let’s imagine we’re working on a new email feature for a user management system:
// Feature branch derived from main branch
A---B---C (feature)
/
X---Y---Z (main)
In commit A, we added a configuration class for mail settings, the mail sender class in B, and in commit C we now use these two new classes to actually send an email on password reset. During testing, we notice that a small error has crept into our sender (commit B) that we now need to fix.
We could simply fix the error and commit the fix as D, which would lead to the following history:
d0297c fix sender and receiver mixup in email sender
c028f4 add mailing functionality for password reset
b032c2 add email sender class
a0f034 add configuration options for email
Note: The commit hashes in this example aren’t real and always start matching the commits mentioned in the text for easier identification.
Mind you: We’re still at the first implementation of the feature. For our future selves, it’s not really relevant to know that we just mixed up the sender and receiver parameters of a function call. It would be nicer if we could “smuggle” the fix directly into B, so the file was added error-free from the start.
The Repair
As explained in articles about interactive rebase, we can of course still do this retroactively: We can reorder and squash commits. We can do this right now, or remember for later that D is actually a fix for B. The first option, however, pulls us somewhat out of our workflow (we’re currently thinking about sending emails, not rebasing), and the second option requires a reliable memory.
This is exactly where the fixup workflow comes to the rescue: We don’t need to worry about the rebase right now; we can continue working on our feature. And instead of just committing D and then, despite our elephant memory, perhaps forgetting later that these changes should actually go into B, we can tell Git right now during the commit that it should “smuggle” this commit into B later, with the following command:
git commit --fixup <COMMIT HASH of B>
In our example case: git commit --fixup b032c2.
The history then looks — for the moment — like this:
d01fd6 fixup! add email sender class
c028f4 add mailing functionality for password reset
b032c2 add email sender class
a0f034 add configuration options for email
Let’s imagine we have a few more commits: We also send emails for user registration (commit E), and then we realize we forgot a setting that we need to add to the configuration class (commit F) and use in the sender class (commit G), so we end up with the following history:
// more commits in the feature branch
A---B---C---D---E---F---G (feature)
/
X---Y---Z (main)
Our git log shows the following:
001a8e fixup! add email sender class
f0c72f fixup! add configuration options for email
e08dd7 add mailing functionality for user registration
d01fd6 fixup! add email sender class
c028f4 add mailing functionality for password reset
b032c2 add email sender class
a0f034 add configuration options for email
The Cleanup
Before we push our feature branch now, we want to clean up the history. And since these commits also show fixup! in the log, we’re reminded of it, so we won’t forget this step either.
With a conventional interactive rebase, we would now have to manually reorder and merge the commits. Thanks to our --fixup commits, Git can now suggest all of this automatically. The full command looks like this:
git rebase --interactive --autosquash <base> [<branch>]
Base in our example is the main branch. We don’t need to explicitly specify our feature branch because we’re currently working on it and have already checked it out. Since we can abbreviate the interactive option and don’t have to write it out in full, it’s enough to just type:
git rebase -i --autosquash main
In this interactive rebase, we’re now automatically presented with the following:
pick a0f034 add configuration options for email
fixup f0c72f fixup! add configuration options for email
pick b032c2 add email sender class
fixup d01fd6 fixup! add email sender class
fixup 001a8e fixup! add email sender class
pick c028f4 add mailing functionality for password reset
pick e08dd7 add mailing functionality for user registration
When this is executed, our history afterwards looks like this:
// Feature branch after cleanup
A'---B'---C'---E' (feature)
/
X---Y---Z (main)
e18dd7 add mailing functionality for user registration
c128f4 add mailing functionality for password reset
b132c2 add email sender class
a1f034 add configuration options for email
The result is a very precise and readable history. Our team and our future selves will know exactly what happened later, without the uninteresting oversights and their repairs distracting from the actual feature.
Conclusion
In this article, I’ve explained Git’s fixup workflow: With git commit --fixup <HASH>, we can tell Git that the current changes belong in a different commit specified by the hash. At the end, we use git rebase --interactive --autosquash <base> to have a suggestion worked out for how the commits in our branch should be automatically reordered and merged, and apply it.
As a result, we have a clean history that no longer reveals that there were a few mistakes in between.
Original (in German — published at my employer): Git Fixup: Wie repariere ich meine Historie?

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.