Git – Does Squashing Pull Requests Break Git’s Merging Algorithm?

git

I'm currently working for a company that uses VSTS for managing git code. Microsoft's "recommended" way of merging a branch is to do a "squash merge", meaning that all commits for that branch get squashed into one new commit incorporating all of the changes.

The trouble is, what if I do some changes in one branch for one backlog item, then immediately want to start doing changes in another branch for another backlog item, and those changes depend on the first branch's set of changes?

I can create a branch for that backlog item and base it on the first branch. So far, so good. However, when it comes time to create a pull request for me second branch, the first branch has already been merged into master and because it's been done as a squash merge, git flags up a bunch of conflicts. This is because git doesn't see the original commits that the second branch was based off of, it just sees the one big squash merge and so in order to merge the second branch in to master it tries to replay all the first branch's commits in top of the squash merge, causing lots of conflicts.

So my question is, is there any way to get around this (other than just never basing one feature branch off another, which limits my workflow) or does squash merging just break git's merging algorithm?

Best Answer

With Git, commits

  • are immutable,
  • and form a directed acyclic graph.

Squashing does not combine commits. Instead, it records a new commit with the changes from multiple other commits. Rebasing is similar, but doesn't combine commits. Recording a new commit with the same changes as an existing commit is called history rewriting. But as existing commits are immutable, this should be understood as “writing an alternative history.”

Merging tries to combine the changes of two commit's histories (branches) starting from a common ancestor commit.

So let's look at your history:

                                 F  feature2
                                /
               1---2---3---4---5    feature1 (old)
              /
-o---o---o---A---o---o---S          master

A is the common ancestor, 1–5 the original feature branch, F the new feature branch, and S the squashed commit that contains the same changes as 1–5. As you can see, the common ancestor of F and S is A. As far as git is concerned, there is no relation between S and 1–5. So merging master with S on one side and feature2 with 1–5 on the other will conflict. Resolving these conflicts is not difficult, but it's unnecessary, tedious work.

Because of these constraints, there are two approaches for dealing with merging/squashing:

  • Either you use history rewriting, in which case you will get multiple commits that represent the same changes. You would then rebase the second feature branch onto the squashed commit:

                                     F  feature2 (old)
                                    /
                   1---2---3---4---5    feature1 (old)
                  /
    -o---o---o---A---o---o---S          master
                              \
                               F'       feature2
    
  • Or you don't use history rewriting, in which case you might get extra merge commits:

                                     F  feature2
                                    /
                   1---2---3---4---5    feature1 (old)
                  /                 \
    -o---o---o---A---o---o-----------M  master
    

    When feature2 and master are merged, the common ancestor will be commit 5.

In both cases will you have some merging effort. This effort doesn't depend very much on which of the above two strategies you choose. But make sure that

  • branches are short-lived, to limit how far they can drift from the master branch, and that
  • you regularly merge master into your feature branch, or rebase the feature branch on master to keep the branches in sync.

When working in a team, it is helpful to coordinate who is currently working on what. This helps to keep the number of features under development small, and can reduce the number of merge conflicts.

Related Topic