TLDR: Even With Only 1 Commit, Squash-Merging is Fundamentally Different!
When squash-merging in git, even if the source branch only has one commit, then the result is still drastically different to a "regular" merge that creates a merge commit.
In particular, if the source branch has any child branches that will also be merged into the same target later on, a squash merge may cause conflicts in those later merges.
Also Useful Pre-requisite Knowledge
Also, conflicts occur during a merge when:
- With respect to the nearest common ancestor of the source and target:
- source and target make different changes to the same line
The other day, when merging a PR and choosing between the "Squash and merge" and "Create a merge commit", a comment from a co-worker exposed a big hole in my git-understanding.
The situation
The source branch which I wanted to merge into the target, had its own child branch,
which would also eventually have to be merged into the target.
Or put more visually:
main -->
child --> # 親=main, 1 commit
grandchild # 親=child, 1 commit, OVERWRITES CHANGES IN CHILD
- I was trying to merge
child
intomain
- At a later point,
grandchild
would also need merging intomain
The comment:
You should do a regular merge instead of a squash merge, otherwise We'll probably get a conflict later on when merging granchild
My foolish response:
child only has 1 commit, so wouldn't a squash be the same as a regular merge? And why would a squash cause a conflict at grandchild's merge?
To which my learned co-worker replied
Even if there's only one commit in the source, a squash merge is completely different to a regular merge, if you look at the log graph you'll see the history is different.
I was confused by this reply, and knew I it was time to go back to git-school.
Here are the main points that I newly understood as a result:
What Happens in a "Squash & Merge"?
- A new commit is added to the target branch
- The history of source is not preserved in this commit - it only has one parent in the target branch
- When you look at the log graph for the target branch, its just a single line
What Happens in a Regular Merge? ("Create a merge commit")
- Similary to squash and merge, a single commit is added to the target branch
- However, the source history is preserved, and it has two parent commits - because it's a merge-commit
- when you look at the log graph for the target branch, you see two lines converging, with separate commits remaining on the source, and target branches
Wait, When do Merge Conflicts Occur Exactly?
A merge consists of
- base/common ancestor
- target (merge into)
- source (merge from)
A conflict occurs when, with respect to the base, target and source contain different changes to the same line.
Example of Regular Merge: No Conflict at Grandchild Merge
First, if child is merged into main, creating a merge commit, you get the "after" situation:
This means, when it comes to merging grandchild
into main
:
- base/common-ancestor is B
- target: D (merge commit created when merging child into main)
- source: C
Since a merge commit doesn't contain any changes, there are no changes for the source's changes to conflict with!
→ no conflict!
Example of Squash-Merge: Conflict at Grandchild Merge!
First, if child is squash-merged into main, you get the "after" situation:
This means, when it comes to merging grandchild
into main
:
- base/common-ancestor is all the way back in A
- target: D (commit created when squash-merging child into main)
- source: C
Since source and target now contain conflicting changed with respect to A, you get a conflict!
→ conflict!
Summary
Squash merging a branch that has a child branch into main, means that when the child's nearest common ancestor with main will be further back.
Which means there is a much greate chance of a merge conflict!
On the otherhand, creating a merge commit keeps that child's nearest common ancestor very recent, reducing the chance of a merge conflict.