Git Merge: To Squash Or Fast-Forward?

Taylor Built Solutions Logo

There comes a point when developing a new software feature or fixing a bug where everything has been coded, tested, and reviewed. If git is being used with feature/bug fix branches the next step is the daunting challenge of merging the newly written code with the branch we diverged from. There are several common strategies for handling a merge in git. The ones of interest in this post are fast forward, three way, and squash. What you end up having to ask yourself is:

How do I want to do this?

The merging strategy that you’ll choose actually comes down to this question: Do you want to retain commit history or make it easy to rollback this merge? In any software organization the answer to this question will depend on where you are in your branch structure and what it is that you’re merging. To understand what the differences in these strategies are we should first start with describing what needs to happen when we merge. From there we can discuss how these strategies work. With that understanding we’ll then handle answering this question.

What Does Git Need To Do When Merging Branches?

Let us imagine a developer creates a new branch (feature/test) from another branch (develop) to work on. They make a a change that they commit to their branch to implement their bug fix or feature. When they’re done they need to merge back into the branch they diverged from. If other changes were made to the original branch in the mean time the result may look something like this:

This series of commits shows two commits on the develop branch and one on the feature/test branch of a git repository

If we’re seeking to merge the changes from the feature branch into the develop branch git will need to make sure that each commit from both branches is present on the develop branch after the merge. Git has several merge strategies it can use to make sure that all of the commits end up present, fast forward, three way, and squash included.

How Does Git Handle Diverged Branches? A Merge Commit / Three Way Merge

If there are changes on the develop branch after the feature/test branch diverged it is necessary to create a separate merge commit when merging. It does this to mark that a merge had to happen (whether or not there were any merge conflicts). If there are any conflicts between the code that was added on the two branches, such as changes to the same lines in the same file, git will save a version of the file with some added text to mark the change. The developer will need to resolve the changes and then commit the changes as a merge commit. If there wasn’t a conflict git will simply create a merge commit. This ends up looking like the following:

When merging from one branch to another where there have been changes on both branches, git has to create a merge commit to mark that the merge happened.

What Is A Git Fast Forward Merge?

Now that we know what is supposed to happen during a merge when the branches have diverged, let’s talk about the fast forward merge strategy. The strategy name is pretty descriptive in this case. The fast forward strategy attempts to apply each commit from the feature branch onto the develop branch. When there are no commits on the develop branch after the branches diverged it can do this by simply updating the head pointer of the develop branch to the latest commit that’s on the new branch and call it a day. The benefit of a fast forward merge is that it maintains the commit history on the branch without adding any extra commits. It looks something like this:

This shows a git repository where the feature/test2 branch is ahead of the develop branch but there are no commits on the develop branch after the branches diverged.
Here we see that a fast-forward merge happened to develop where the pointer to the develop's head commit was simply updated to where feature/test2 was pointing.

What Is A Squash Merge?

A squash commit is also aptly named once you know what it does. It takes all of the commits that have happened on a branch and squashes them down into a single commit. Let’s say we start with a bunch of commits on the feature/test2 branch as follows:

Here we see multiple commits on the feature/test2 branch. The history of these commits aren't necessary to keep.

The squash merge is technically two actions: squash and merge. From the command line these would both be handled in the single command git merge --squash feature/test2. GitKraken, however, highlights that these are separate actions. Here you can see that I’ve squashed all of the commits from above into a single commit on feature/test2.

Here we see a single squashed commit of the commits whose history we didn't need to keep.

Because there weren’t any other commits on develop since the two branches diverged git can fast forward develop to the current commit on feature/test2.

Because there have been no commits on develop, we can see here that Git Kraken gives the fast forward option.
Here we see the head of the develop and the head of the feature/test2 branches both pointing to the same commit.

When Would We Want To Use These Different Merge Strategies?

Now that we’ve seen these merging strategies in action we can talk about when and why you’d want to use each. As usual, it depends. Each of the strategies is useful in different situations. The short version is that a fast forward or three way merge is useful on a branch where you want to keep the history of commits and squash where you don’t. Further discussion from here presumes that there is a develop branch that is current with what the developers are working on and some hierarchy of branches above develop representing the current state of environments that are released to (QA, UAT, Production, etc).

The likely candidate for a squash merge is when a developer is merging code they’ve been working on in a feature or bugfix branch diverged from the develop branch back into develop. The feature/bugfix branch is likely to have lots of commits on it that represent the daily work of the developer. This commit history isn’t necessarily important at a higher level view of the project and, as such, it doesn’t matter for the higher level branches either. To the contrary, the feature itself as a whole is what is important. Given that a squash merge will condense the entire feature down to a single commit not only removes the commit history that might be considered noise but allows for easy roll back of the feature by reverting a single commit.

If features are squash merged into the develop branch we’ll end up with a series of commits that represent the addition of entire features or bug fixes. This is commit history that matters to the high level view of the project. This makes using fast forward merges when merging from develop up to QA/UAT/Prod branches make lots of sense. If we can fast forward without introducing merge commits on these branches even better.

Conclusion – Squash Feature Commits and Fast Forward After That

There is a time and a place for every kind of commit. The only additional piece of advice that I would add to this is: Merge develop into your feature branch before you squash merge. By doing this your squash merge into develop can be fast forwarded and avoid having an additional merge commit. Once on develop you can move your features up through your environment easily.

For another interesting read check out my post on how I approach writing software.

Leave a Reply

Your email address will not be published. Required fields are marked *