git pick and squash

PUBLISHED ON 16-04-2016

When a merge request is created on a project, more often than not there will be a review which will require you to change the code, thus creating one or more extra commits. After multiple rounds of review and changes to your code someone might ask you something along the lines of: ‘Could you squash your commits?’.

What is asked of you is to combine at least two commits. This reduces the number of commits which will be included in the default branch, usually named master, which hopefully will make it easier to find commits later which fixed or introduced bugs. The reasoning: fewer events in the history makes sure there is a clean history.

Let’s look at an example, here you have a merge request with as source branch feature-1 to be merged in master.

master      01
              \
feature-1     02 - 03 - 04

After squashing this would look like:

master      01
              \
feature-1     02

And after merging a clean history of only two nodes instead of four.

master      01 - 02

Lets dive in the commands to make this happen. Given you have got the feature-1 branch checked out, lets look at a fictitious history (git log):

commit d32fb435ebda705593660cd69073c2e2861385f0
Author: Zeger-Jan van de Weg <zegerjan@gitlab.com>
Date:   Fri Apr 1 21:08:11 2016 +0200

    Fix typo on docs

commit 61b76dea0eb225856d22d1052893d254510af098
Author: Zeger-Jan van de Weg <zegerjan@gitlab.com>
Date:   Fri Apr 1 21:07:48 2016 +0200

    Documentation on feature 1

commit 178fe07bf70acc48e055007c439db586ff318388
Author: Zeger-Jan van de Weg <zegerjan@gitlab.com>
Date:   Fri Apr 1 21:07:11 2016 +0200

    Feature 1

commit c437b15c137b099f478263d781a22e82daae1475
Author: Zeger-Jan van de Weg <zegerjan@gitlab.com>
Date:   Fri Apr 1 21:05:32 2016 +0200

    Initial commit

In this example, the ‘Initial commit’ is made on master after which we branched to the feature-1 branch on which an additional three commits where made. The last 3 we are going to combine into one. Rewriting history is done by the git subcommand rebase. Personally I always use the interactive mode, and would advise you to do the same.

git rebase --interactive HEAD~3 HEAD – mind the tilde, not a dash! The last two arguments passed to git, HEAD~3, gives the range we are going to rewrite. From 3 commits ago until the current HEAD. The last argument can be dropped if you mean to tell HEAD and interactive has a shorthand also, leaving git rebase -i HEAD~3.

Next you’ll be displayed the list of commits in the range, in the reverse order that git log displayed them.

pick 178fe07 Feature 1
pick 61b76de Documentation on feature 1
pick d32fb43 Fix typo on docs

Creating one commit is done by replacing the words pick before the short sha of each commit by either fixup or squash. Their key difference is the way git will handle the commit message. When using fixup, or just f for short, the message will be discarded, changes will be combined with the first commit above it which will be retained using that commits message. When you use squash you can later edit the message.

For this example I went with the following

pick 178fe07 Feature 1
squash 61b76de Documentation on feature 1
fixup d32fb43 Fix typo on docs

After saving and closing the buffer, git will present you a new screen, with most notably the following information reflecting our choices just made.

# This is a combination of 3 commits.
# The first commit's message is:
Feature 1

# This is the 2nd commit message:

Documentation on feature 1

# The 3rd commit message will be skipped:

#       Fix typo on docs

Combining the commit messages into one you can save the following as only text in the buffer: Feature 1 with documentation with a clean history:

commit 300cdede90d46867065f3607620950c946b6cd34
Author: Zeger-Jan van de Weg <zegerjan@gitlab.com>
Date:   Fri Apr 1 21:07:11 2016 +0200

    Feature 1 with documentation

commit c437b15c137b099f478263d781a22e82daae1475
Author: Zeger-Jan van de Weg <zegerjan@gitlab.com>
Date:   Fri Apr 1 21:05:32 2016 +0200

    Initial commit

No harm no foul right? Well, there are a couple of problems with rebasing. To start of, you’re lying to anyone with access to the project on what exactly happened. In our example, we now state that all changes were made on on 21:07 o’clock. But we know that to be a lie. You yourself could verify what happened using the reflog – interesting subject for another blog – but the ref log is not propagated to others when you push.

To me rebasing is ok only on feature branching, where only one person works on. If there is uncertainty on either of the premisses, just don’t do it as the pro’s will now not outweigh the cons.