gitgit-mergegit-squash

How does git handle squash merge vs normal merge?


Let's say we have a Pull Request merging the commits from branch A into branch B, and we can perform the merge with normal merge and squash merge. And if we first perform the merge with squash merge (all the commits will be combined into only one commit) and then submit another similar PR from branch A to branch B, why does git still allow the merge in the normal way (all the commits will be kept)? I mean the changes have already been merged into branch B with the squash merge, and why does it not cause any conflict when having the 2nd merge in the normal way?


Solution

  • When you squash, git does not keep any information (other than, perhaps, the comment) about the commits that were merged so, unlike a real merge, git cannot know that the original branch was merged already. That's why it is discouraged to use squashes when you are dealing with long-runnning branches.

    In a real merge, the common ancestor between two branches that have been merged changes but in a squash-merge the common ancestor does not move so later merges between the 2 branches will easily produce conflicts, either conflicts that were taken care of in previous squash-merges or new conflicts.

    To explain it graphically, suppose you have this for starters:

      * GGG blah blah (other-branch)
      * FFF
      * EEE
    * | DDD (main)
    * | CCC
    * | BBB
    |/
    * AAA
    

    At this point, what is the latest common ancestor? AAA, right?

    Now, suppose you do a real merge, we get something like this:

    * HHH (main)
    |\
    | * GGG (other-branch)
    | * FFF
    | * EEE
    * | DDD
    * | CCC
    * | BBB
    |/
    * AAA
    

    Good. What is the latest common ancestor?

    Tip of he answer: It's GGG. Make sure you digest that before moving on.

    Now, suppose you keep on working on both branches and you end up with this:

    * NNN (main)
    * MMM
    * LLL
    | * KKK (other-branch)
    | * JJJ
    | * III
    * | HHH
    |\|
    | * GGG
    | * FFF
    | * EEE
    * | DDD
    * | CCC
    * | BBB
    |/
    * AAA
    

    If you tried to merge again, git would need to consider the changes after the last common ancestor, which we already know is GGG, right? So, git would need to consider this for the merge:

    * NNN (main)
    * MMM
    * LLL
    | * KKK (other-branch)
    | * JJJ
    | * III
    * | HHH
    |/
    * GGG
    

    Now, let's go back to see how it would be if we had squash-merged instead. After the first squash-merge, we would get:

    * HHH squash merge (main)
    | * GGG (other-branch)
    | * FFF
    | * EEE
    * | DDD
    * | CCC
    * | BBB
    |/
    * AAA
    

    And, now.... what is the latest common ancestor? It's still AAA,... and now, on both branches you have a lot of common code... and not so common code that might have been adjusted from conflict resolution because of the squash-merge in HHH. How it would look if you had continued working on both branches?

    * NNN (main)
    * MMM
    * LLL
    | * KKK (other-branch)
    | * JJJ
    | * III
    * | HHH squash merge
    | * GGG
    | * FFF
    | * EEE
    * | DDD
    * | CCC
    * | BBB
    |/
    * AAA
    

    If you tried to merge, git would have to start over considering the changes from AAA, not GGG, as it happened before.... and given that you have a lot of common code coming from the squash and it's very likely that both branches might have touched those sections of code (which makes them different from git's POV), then you will get a bunch of conflicts.... it's actually very likely you will get the same conflicts you got when you did the first squash merge(content on each branch will be a little bit different from the original conflict, actually... but it will be the same section of code) plus a few more... just for the fun of it.

    So, all in all... it's ok to squash, but it should be done for short-lived branches like feature branches that you work on and you kill them once they are merged.... if you are dealing with long-running branches, make sure to use real merges, unless you would like to take a peek at what hell looks like.

    Now, about there not being any conflicts: git will not produce a conflict if exactly the same change is coming from the branches being merged.... If you squashed and then try to merge the real branch (without additional changes) then to git the same thing is coming from both branches so it's ok. There are scenarios (like when cherry-picking) when git complains about there not being any real change being introduced by the cherry-pick operation and then you need to decide what to do (skip it, create am empty commit)... This is an scenario I'd like to see if git does not complain about and allows the merge to go just like that.