If you work with a team and use Git as your primary version control system, then you may have noticed some strange commit messages in your log. These commits have cryptic messages like "Merge remote-tracking branch 'origin/master'" that are pretty much meaningless. In this post we'll see an example of how these ugly commits are generated and how to prevent them using the git rebase command. This example assumes you are familiar with basic Git commands and workflows. Having a clean commit history is not just for developers with CDO (that's OCD in alphabetical order like it should be).

Imagine Alice and Bob are collaborating on a project. There is a hosted remote repository (on github.com perhaps) and both Alice and Bob have made a local clone of the repository. The "origin" remote points to the hosted repository.

First, Alice makes some changes and commits them to her local master branch.

git commit -am "Issue #1 by Alice: some cool changes"

She then pushes her commit to the hosted repository.

git push origin master

Alice's local commit history (git log) now looks like this:

commit 1d990653833ca8581b5883e19c0a76ab506aa884
Author: Alice
Date:   Fri Feb 8 13:36:12 2013 -0700
    Issue #1 by Alice: some cool changes

At the same time, Bob is making changes to his own local repository. He finishes editing and makes a commit.

git commit -am "Issue #2 by Bob: awesome stuff"

So Bob's local commit history looks like this:

commit d0acdd37fe6ba13743942039260a5851e6e27ba7
Author: Bob
Date:   Fri Feb 8 13:37:51 2013 -0700
    Issue #2 by Bob: awesome stuff

Unfortunately, Bob's changes took longer than Alice's and she has already pushed her changes to the hosted repository. Bob needs to merge in those changes before he can push his own. To accomplish this, he first fetches Alice's changes from the hosted repository and then merges them into his local branch using git merge.

$ git fetch origin
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
   1d9906..06aa884  master     -> origin/master
$ git merge origin/master
Auto-merging README.txt
Merge made by the 'recursive' strategy.
 README.txt |    2 ++
 1 file changed, 2 insertions(+)

Notice the message "Merge made by the 'recursive' stratagy". This means that Git took Alice's commit from origin/master and Bob's local commit and squished them together into a third commit (insert squishing sound here). This new commit is called a "merge" commit and Bob's local commit history now looks like this:

commit 01b2643780f049f1ee99b21b68ed766b2bcec549
Merge: d0acdd3 1d99065
Author: Bob
Date:   Fri Feb 8 13:41:37 2013 -0700
    Merge remote-tracking branch 'origin/master'
 
commit d0acdd37fe6ba13743942039260a5851e6e27ba7
Author: Bob
Date:   Fri Feb 8 13:37:51 2013 -0700
    Issue #2 by Bob: awesome stuff
 
commit 1d990653833ca8581b5883e19c0a76ab506aa884
Author: Alice
Date:   Fri Feb 8 13:36:12 2013 -0700
    Issue #1 by Alice: some cool changes

Now imagine this happening over and over again throughout the life of the project. Thousands of merge commits look ugly and make it difficult to follow the history of the project.

So let's rewind a bit and try something else. Alice has made her changes, committed them, and pushed them to the hosted repository. Bob has also made his changes and committed them locally but still needs to merge in Alice's commit. Just like before, he runs git fetch to retrieve Alice's changes from the remote repository.

$ git fetch origin
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
   1d9906..06aa884  master     -> origin/master

This time, however, Bob uses the rebase command instead of doing a merge and creating that ugly merge commit.

$ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: Issue #2 by Bob:  awesome stuff

And that's it. The magic has happened and Bob's local commit history looks nice and clean without any merge commits.

commit d0acdd37fe6ba13743942039260a5851e6e27ba7
Author: Bob
Date:   Fri Feb 8 13:37:51 2013 -0700
    Issue #2 by Bob: awesome stuff
 
commit 1d990653833ca8581b5883e19c0a76ab506aa884
Author: Alice
Date:   Fri Feb 8 13:36:12 2013 -0700
    Issue #1 by Alice: some cool changes

What actually takes place behind the scenes is quite simple. Instead of trying to smash two commits together, Git first undoes Bob's changes before applying Alice's. Then, after Alice's commit, git reapplies Bob's changes leaving a nice clean history in the log.

For more detailed information on the rebase command, see http://git-scm.com/book/en/Git-Branching-Rebasing.