What does it mean to squash commits in git?

You can think of Git as an advanced database of snapshots of your working directory(ies).

One very nice feature of Git is the ability to rewrite the history of commits.
The principal reason for doing this is that a lot of such history is relevant only for the developer who generated it, so it must be simplified, or made more nice, before submitting it to a shared repository.

Squashing a commit means, from an idiomatic point of view, to move the changes introduced in said commit into its parent so that you end up with one commit instead of two (or more).
If you repeat this process multiple times, you can reduce n commit to a single one.

Visually, if you started your work at the commit tagged Start, you want this

Git commits squashing

You may notice that the new commit has a slightly darker shade of blue. This is intentional.

In Git squashing is achieved with a Rebase, of a special form called Interactive Rebase.
Simplifying when you rebase a set of commits into a branch B, you apply all the changes introduced by those commits as they were done, starting from B instead of their original ancestor.

A visual clue

enter image description here

Note again the different shades of blue.

An interactive rebase let you choose how commits should be rebased. If you run this command:

 git rebase -i branch

You would end up with a file that lists the commits that will be rebased

 pick ae3...
 pick ef6...
 pick 1e0...
 pick 341...

I didn't name the commits, but these four ones are intended to be the commits from Start to Head

The nice thing about this list is that it is editable.
You can omit commits, or you can squash them.
All you have to do is to change the first word to squash.

 pick ae3...
 squash ef6...
 squash 1e0...
 squash 341...

If you close the editor and no merge conflicts are found, you end up with this history:

enter image description here

In your case, you don't want to rebase into another branch, but rather into a previous commit.
In order to transform the history as shown in the very first example, you have to run something like

git rebase -i HEAD~4

change the "commands" to squash for all the commits apart from the first one, and then close your editor.


Note about altering history

In Git, commits are never edited. They can be pruned, made not reachable, cloned but not changed.
When you rebase, you are actually creating new commits.
The old ones are not longer reachable by any refs, so are not shown in the history but they are still there!

This is what you actually get for a rebase:

enter image description here

If you have already pushed them somewhere, rewriting the history will actually make a branch!


The rebase command has some awesome options available in its --interactive (or -i) mode, and one of the most widely used is the ability to squash commits. What this does is take smaller commits and combine them into larger ones, which could be useful if you’re wrapping up the day’s work or if you just want to package your changes differently. We’re going to go over how you can do this easily.

A word of caution: Only do this on commits that haven’t been pushed an external repository. If others have based work off of the commits that you’re going to delete, plenty of conflicts can occur. Just don’t rewrite your history if it’s been shared with others.

So let’s say you’ve just made a few small commits, and you want to make one larger commit out of them. Our repository’s history currently looks like this:

enter image description here

The last 4 commits would be much happier if they were wrapped up together, so let’s do just that through interactive rebasing:

$ git rebase -i HEAD~4

pick 01d1124 Adding license
pick 6340aaa Moving license into its own file
pick ebfd367 Jekyll has become self-aware.
pick 30e0ccb Changed the tagline in the binary, too.

# Rebase 60709da..30e0ccb onto 60709da
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

So, a few things have happened here. First of all, I told Git that I wanted to rebase using the last four commits from where the HEAD is with HEAD~4. Git has now put me into an editor with the above text in it, and a little explanation of what can be done. You have plenty of options available to you from this screen, but right now we’re just going to squash everything into one commit. So, changing the first four lines of the file to this will do the trick:

pick 01d1124 Adding license
squash 6340aaa Moving license into its own file
squash ebfd367 Jekyll has become self-aware.
squash 30e0ccb Changed the tagline in the binary, too.

Basically this tells Git to combine all four commits into the the first commit in the list. Once this is done and saved, another editor pops up with the following:

# This is a combination of 4 commits.
# The first commit's message is:
Adding license

# This is the 2nd commit message:

Moving license into its own file

# This is the 3rd commit message:

Jekyll has become self-aware.

# This is the 4th commit message:

Changed the tagline in the binary, too.

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # Explicit paths specified without -i nor -o; assuming --only paths...
    # Not currently on any branch.
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #   new file:   LICENSE
    #   modified:   README.textile
    #   modified:   Rakefile
    #   modified:   bin/jekyll
    #

Since we’re combining so many commits, Git allows you to modify the new commit’s message based on the rest of the commits involved in the process. Edit the message as you see fit, then save and quit. Once that’s done, your commits have been successfully squashed!

Created commit 0fc4eea: Creating license file, and making jekyll self-aware.
 4 files changed, 27 insertions(+), 30 deletions(-)
  create mode 100644 LICENSE
    Successfully rebased and updated refs/heads/master.

And if we look at the history again… enter image description here

So, this has been a relatively painless so far. If you run into conflicts during the rebase, they’re usually quite easy to resolve and Git leads you through as much as possible. The basics of this is fix the conflict in question, git add the file, and then git rebase --continue will resume the process. Of course, doing a git rebase --abort will bring you back to your previous state if you want. If for some reason you’ve lost a commit in the rebase, you can use the reflog to get it back.

Details can be found this link.

Tags:

Git

Github