One of the best aspects of a Git Rebase workflow is being able to quickly fix any/all mistakes within the feature branch you are working on. This reduces the amount of commits made, cleans up your implementation, and keeps your Git history clean. Having a clean history is important for professional engineering.
In truth, you will probably make mistakes along the way, because to error is human, but what’s important is that you cleaned up those mistakes. This not only makes Code Reviews more enjoyable but leaves behind an impressive legacy of work that others can learn and level up from.
Now that you know why fixing your commits are important, let’s learn how to fix any/all mistakes. To start, we’ll create a new repository with an initial commit:
mkdir demo
cd demo
git init
printf "%s\n" "= A demonstration project." > README.adoc
git add .
git commit --message "documentation"
Next, we’ll create a feature branch and work on a simple Ruby implementation:
git switch --create calculator --track
printf "%s\n\n" "# frozen_string_literal: true" > calc.rb
printf "%s\n" 'ARGV.then { |a, b| puts "#{a} plus #{b} equals #{a.to_i + b.to_i}." }' >> calc.rb
git add calc.rb
git commit --message "Added calculator implementation" \
--message "Necessary to calculate the addition of two numbers."
Now we can test our implementation:
ruby calc.rb 1 2
# 1 plus 2 equals 3.
Simple but effective. There’s a problem, though. We need to print the mathematical calculation instead of sentences. To fix, you can edit the calc.rb file and replace the original implementation with the following:
ARGV.then { |a, b| puts "#{a} + #{b} = #{a.to_i + b.to_i}." }
If we run the above, we’ll get the desired result:
ruby calc.rb 1 2
# 1 + 2 = 3.
We can also make a commit with this fix:
git commit --all --message "Fixed implementation"
If we look at our Git history, by running git log --oneline, we get a result that looks similar to the following:
a93dc50713f0 (HEAD -> calculator) Fixed implementation
5fc31dea2b51 Added calculator implementation
cb58f9c68a3b (main) documentation
Our last commit, "Fixed implementation", is our fixup. We can fold this commit into the "Added calculator implementation" commit by rebasing:
git rebase --interactive
You’ll now have something similar in your rebase editor:
p 5fc31dea2b51 Added calculator implementation p a93dc50713f0 Fixed implementation
We only need to change "Fixed implementation" from p (pick) to f (fixup). By the way, I’m using Git Rebase Abbreviations which is why you see single letter instructions. With these changes applied you should have the following in your rebase editor:
p 5fc31dea2b51 Added calculator implementation f a93dc50713f0 Fixed implementation
If you save and exit, the rebase will conclude and you can look at your Git history to confirm the following:
571d1584a540 (HEAD -> calculator) Added calculator implementation
cb58f9c68a3b (main) documentation
Notice the fixup commit is gone. Even better, the implementation is correct:
git show
# # frozen_string_literal: true
#
# ARGV.then { |a, b| puts "#{a} + #{b} = #{a.to_i + b.to_i}." }
Here’s what’s nice about fixing commits like this:
-
We fixed the original implementation while erasing our mistake.
-
We avoided editing the commit message because it was an implementation change only.
-
Reviewers will have a high signal to noise when providing feedback.
-
Our implementation looks as if it was crafted perfectly the first time.
We can take this a step further, though. While everything demonstrated thus far is fantastic, we can work smarter by using git commit --fixup. Let’s say we realized we weren’t suppose to implement addition but multiplication instead. Let’s fix our implementation by using git commit --fixup instead. First, update calc.rb with this code:
ARGV.then { |a, b| puts "#{a} * #{b} = #{a.to_i * b.to_i}." }
Since we already know our SHA when last looking at our Git history, we can make this fix and immediately rebase as follows:
git commit --all --fixup 571d1584a540
git rebase --interactive
The above will yield the following in your rebase editor:
p 571d1584a540 Added calculator implementation f 1e03bc695b17 fixup! Added calculator implementation
Notice the fixup! commit. That is Git’s special directive for automatically making a fixup commit so you don’t have to manually move the fixup next to the commit that needs fixing or changing p to f. To emphasize, this is made possible via the following configuration:
Since we don’t need to do anything in our rebase editor, you can save and exist to confirm the following:
git log --oneline
# 1711cbe79536 (HEAD -> calculator) Added calculator implementation
# cb58f9c68a3b (main) documentation
ruby calc.rb 2 3
# 2 * 3 = 6.
This unleashes your ability to make multiple fixup! commits and eventually rebasing to fold them all in before pushing up for code review (or even during code review to keep the changes clean).
Now, you might be thinking: "This is great but could I avoid using the Git Rebase Editor altogether?" Yes, you can! You only need to prefix GIT_EDITOR=true before performing your rebase. This might seem counterintuitive because true looks like you want the editor to launch but is the opposite and disables the editor instead. Let’s run through this one last time by making a fixup to our implementation to calculate subtraction instead. First, start by editing calc.rb with this code:
ARGV.then { |a, b| puts "#{a} - #{b} = #{a.to_i - b.to_i}." }
Then do the following:
# Makes a fixup commit by grabbing the SHA of the last commit off of our "calculator" branch.
git commit --all --fixup "$(git rev-parse --short calculator)"
GIT_EDITOR=true git rebase --interactive
git log --oneline
# 1d10a0212e61 (HEAD -> calculator) Added calculator implementation
# cb58f9c68a3b (main) documentation
ruby calc.rb 4 1
# 4 - 1 = 3.
Perfect!
There’s still one outstanding issue we should address before wrapping up and that’s the first commit we made. It’s poorly written. We also haven’t pushed our newly minted repository to a remote server so we can fix up our main branch too (‼️ Only do this on a repository that’s never been pushed to a remote server). Here’s how:
# Switch back to the `main` branch.
git switch main
# Merge while avoiding a merge commit because those are ugly and unnecessary.
git merge --ff-only calculator
# We don't need the calculator feature branch, so we can delete it.
git branch --delete --force calculator
git log --oneline
# 1d10a0212e61 (HEAD -> main) Added calculator implementation
# cb58f9c68a3b documentation
# Use `--root` to rebase to the first commit of the repository.
# Then reword "documentation" as "Added documentation".
git rebase --interactive --root
git log --online
# 835326ac446f (HEAD -> main) Added calculator implementation
# 258f0a2e2580 Added documentation
That’s a clean and well organized, history. To recap, you’ve learned:
-
How to fix a commit using the Git Rebase Editor.
-
How to fix a commit by making a
fixup!commit. -
How to rebase
fixup!commits without opening your editor. -
How to rebase your main branch (super handy when you’ve not pushed to a remote).
💡 Check out Git Lint if you’d like to automate keeping your Git history consistent and clean.
Enjoy!