The letter A styled as Alchemists logo. lchemists
Published April 15, 2025 Updated April 15, 2025
Cover
Git Rebase Squash

Squashing commits is one of the most misused features of Git Rebase because it’s the root of so many bad practices in Git. Doesn’t help that GitHub promotes these bad practices by default. Here’s a few reasons why squashing should be used with great care (and this is by no means exhaustive):

  • Degrades the readability of your Git history.

  • Reduces the searchability of your Git logs via git log --grep and/or git log -S.

  • Turns git bisect and git revert into a total nightmare when unwinding entangled changes.

You might be thinking that you should never squash and you’d be partially correct. The truth is that squashing commits should be used sparingly and, if you keep your commits atomic (see Git Commit Anatomy), you’ll find that using Git Rebase Fixup is faster and far superior.

With the above in mind, let’s look at how you can cleanly squash your commits without incurring misery for yourself and team. 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 "Added project skeleton" \
           --message "Provides initial project structure with implementation to be added later."

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."

We can run our implementation as follows:

ruby calc.rb 1 2
# 1 plus 2 equals 3.

Simple but effective. Unfortunately, we made a mistake because we need to print the result as an equation instead of a sentence. We can correct our mistake by updating our calc.rb implementation to use the following instead:

ARGV.then { |a, b| puts "#{a} + #{b} = #{a.to_i + b.to_i}" }

Running the above, will result in the following:

ruby calc.rb 1 2
# 1 + 2 = 3

Much better, let’s commit these changes:

git commit --all

When your commit editor pops up, use this commit message:

Updated calculator implementation to print equation

Necessary to prefer displaying an equation instead of a sentence.

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 1 2`
* *Result*:  `1 + 2 = 3`

If you use git log --oneline, you should see the following in your Git log:

35519d72a465 (HEAD -> calculator) Updated calculator implementation to print equation
b9da7eb992a9 Added calculator implementation
1b8183aa675b (main) Added project skeleton

You’ll notice the last two commits are changes to the same implementation (i.e. calc.rb). We don’t need separate commits for this work because we are making changes to the same implementation. This is where squashing these commits makes perfect sense because:

  • We can keep our commit history clean by ensuring calc.rb is a single commit with the correct implementation and — even better — all mistakes are erased.

  • We need to fold the two commits messages into a single, cohesive, message that succinctly sums up the work (no need to spread this information across multiple commits).

Now that you understand the why, let’s apply the changes by starting our interactive rebase:

git rebase --interactive

You should now see the following in your Git Rebase Editor:

p b9da7eb992a9 Added calculator implementation
p 35519d72a465 Updated calculator implementation to print equation

💡 I’m using Git Rebase Abbreviations if you’re wondering why each p isn’t pick.

All we need to do is change p to s for "Updated calculator implementation to print equation". Once you do that, save the changes, and then exit your editor, you should see the following commit message show up in your editor:

# This is a combination of 2 commits.
# This is the 1st commit message:

Added calculator implementation

Necessary to calculate the addition of two numbers.

# This is the commit message #2:

Updated calculator implementation to print equation

Necessary to prefer displaying an equation instead of a sentence.

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 1 2`
* *Result*:  `1 + 2 = 3`

Unlike Git Rebase Fixup, squash will always prompt you to fix up your commit message. This is perfect because we need some of what’s in both of of the original commit message to make our final commit message as follows:

Added calculator implementation

Necessary to calculate the addition of two numbers.

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 1 2`
* *Result*:  `1 + 2 = 3`

Now you can save and exit to apply these changes. Notice that the above applied the following changes:

  • We took the subject from the first commit.

  • We deleted subject and first sentence from the second commit message but kept the bullet points because they provide detail and clarity we didn’t have in the first commit.

This is the true power of squashing because you can apply changes to the same file and combine multiple messages into a single cohesive message. The key is to always keep your commits small and atomic as described in the Git Commit Anatomy article.

Here’s how all of this looks from our Git history:

git log --oneline

# 5beacf65d9c0 (HEAD -> calculator) Added calculator implementation
# 1b8183aa675b (main) Added project skeleton

git show

# commit 5beacf65d9c0ad607059437f17b9acea172c87b0 (HEAD -> calculator)
# Author: Brooke Kuhlmann <brooke@alchemists.io>
# Date:   Sun Apr 6 15:06:07 2025 -0600
#
#     Added calculator implementation
#
#     Necessary to calculate the addition of two numbers.
#
#     * *Usage*:   `ruby calc.rb <number> <number>`
#     * *Example*: `ruby calc.rb 1 2`
#     * *Result*:  `1 + 2 = 3`
#
#
# added: calc.rb
# ────────────────────────────────────────────────
#
# # frozen_string_literal: true
#
# ARGV.then { |a, b| puts "#{a} + #{b} = #{a.to_i + b.to_i}" }
# ────────────────────────────────────────────────

In short, squashing allows you to apply changes to the same file (or atomic change) and edit your commit message in one fell swoop.

So far, I’ve showed you how to use Git Rebase to squash your changes but you can do this via a single commit too. To illustrate, let’s say you made another mistake and realized you should be using multiplication instead of addition. You can use git commit --squash <sha> to accomplish what we did earlier while only using a single commit. First, let’s fix the implementation by editing calc.rb to have this implementation:

ARGV.then { |a, b| puts "#{a} * #{b} = #{a.to_i * b.to_i}" }

Since we know our last commit SHA was 5beacf65d9c0 (yours will be different), we can use the following to commit these changes:

git commit --all --squash 5beacf65d9c0

You’ll now be prompted to add a commit message for which we can use the following:

squash! Added calculator implementation

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 2 3`
* *Result*:  `2 * 3 = 6`

Notice, I documented the changes (we’ll come back to this soon). If we save and exit our editor, you should see the following Git history:

git log --oneline

Now, if we look at our Git history, you’ll see something similar to the following:

git log --oneline

# e6e478ea6efa (HEAD -> calculator) squash! Added calculator implementation
# 5beacf65d9c0 Added calculator implementation
# 1b8183aa675b (main) Added project skeleton

Now we can rebase to fold in the changes:

git rebase --interactive

💡 I have Git Rebase AutoStash configured to automatically move squashed commits to the appropriate commit to be squashed.

You should see the following in your Git Rebase Editor:

p 5beacf65d9c0 Added calculator implementation
s e6e478ea6efa squash! Added calculator implementation

If you recall, when we first squashed, we had to manually change p to s but, with our squash! Git directive, Git handled all of this for us. Thanks Git! Now we can save and exit to see this commit message pop up in our editor:

# This is a combination of 2 commits.
# This is the 1st commit message:

Added calculator implementation

Necessary to calculate the addition of two numbers.

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 1 2`
* *Result*:  `1 + 2 = 3`

# This is the commit message #2:

# squash! Added calculator implementation

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 2 3`
* *Result*:  `2 * 3 = 6`

Once again, we’ve got both commit messages but only need to make a single, cohesive, commit message because leaving this as is would be extremely confusing, incorrect, and embarrassing. All we need is the second bullet points which means we can clean this up to look like this:

Added calculator implementation

Necessary to calculate the multiplication of two numbers.

* *Usage*:   `ruby calc.rb <number> <number>`
* *Example*: `ruby calc.rb 2 3`
* *Result*:  `2 * 3 = 6`

Notice I changed "addition" to "multiplication" for correctness. Now if we look at our Git history, everything is clean again:

git log --oneline

# 4f395a890591 (HEAD -> calculator) Added calculator implementation
# 1b8183aa675b (main) Added project skeleton

To recap, you’ve learned how to squash using Git rebase and also using git commit --squash. Both are highly effective especially when you need to make changes to your implementation and combine and/or update your commit message. Otherwise, you’ll want to use Git Rebase Fixup instead.

What you don’t want to do is squash multiple, unrelated changes, into a massive single commit because then you rob yourself of Git’s arsenal of tools to debug, revert, and fix changes quickly and easily.

May your Git history be clean and a joy to read. Enjoy!