What are
the necessary and sufficient conditions, and/or
all the cases or some common cases
that can cause git merge
report merge conflict?
How does git merge
determine whether a line or some lines contain
merge conflict(s)?
For example, I sometimes see cases like the following, where either
Part 1
or Part 2
is empty in
<<<<<<< HEAD
(Part 1)
=======
(Part 2)
>>>>>>> some-other-branch
It looks unlikely to have a merge conflict to me. So what are some possible reasons that cases like that have merge conflicts?
Comparing the merge conflicts reported by git merge
and the
differences reported by git diff
, is it correct that
the differences reported by git diff
might not necessarily be in the places of the merge conflicts reported by git merge
, and
the merge conflicts reported by git merge
might not necessarily be in the places of the differences reported by git
diff
?
Thanks.
You can only resolve merge conflicts on GitHub that are caused by competing line changes, such as when people make different changes to the same line of the same file on different branches in your Git repository. For all other types of merge conflicts, you must resolve the conflict locally on the command line.
There are multiple parts to a complete, correct answer. First, we have to get to a normal three way merge in the first place (which in Git requires using -s recursive
or -s resolve
, and if using -s recursive
, finding a single merge base and two other commits). You may, however, want to skip right down to the third section.
To do any merge at all, you need:
HEAD
) that has B as an ancestor,Since "is ancestor" allows node equality in the commit graph (technically it's a predecessor-or-equal ≼ comparison), it's possible to have B = L and/or B = R. If this is the case, however, then no merging is ever required, and if you do force a merge (using git merge --no-ff
—this implies B = L and L ≺ R; the two unforced cases are "fast forward" not-actually-a-merge and a "nothing to merge" error) there will be no merge conflicts. So we may as well assume that the merge base precedes both sides of the merge.
1Using --allow-unrelated-histories
, you can have Git substitute in the empty tree for an actual base commit, if L and R have no lowest common ancestor nodes. This, however, causes all identified files to be add/add conflicts.
Next, to get a conflict in some file path, you need either what I call a "high level" conflict or a "low level" conflict (or both). For this to occur, Git must identify (i.e., match together) files in B, L, and R. Due to the ability to add new files, rename files, and delete files, this does not require that the file have the same name in all three commits. In particular:
path/to/foo.txt
, that has a matching PL path/to/foo.txt
and PR path/to/foo.txt
. Obviously this file is "the same file" all throughout the merge, so Git identifies the three paths as one file.)path/to/basename
, PL path2/to2/left
, and PR path3/to3/right
. One or more of these may not even exist. These result in: add/add conflict, if ∄ PB and PL = PR; a rename/delete conflict, if PB equals one of the left or right paths, but the other does not exist; or a rename/rename conflict, if PB ≠ PL ≠ PR.If there is not already a high level conflict (add/add, rename/rename, or rename/delete), there may still be a rename/modify or rename/delete conflict, as long as the hash IDs of the blobs (file contents) named by the two or three path names do not match.
All of these "high level" conflicts cause a merge conflict, but do not by themselves result in any conflict markers. To get that, we must now actually merge the base file with the two side files (which means all three files must exist, or for the add/add case, we take a trivial empty file as the base version).
This merge may or may not have its own conflicts. If the merge adds low level conflicts, we will get conflict markers. If there is no high level conflict (the path is the same in all three commits) but a full merge is required (the hashes all differ), we may get a low level conflict with conflict markers.
To get a merge conflict within one file in the work-tree, Git must see the same line changed by both left and right side versions, but changed in different ways. (Remember, all three hashes must differ, so that Git is combining a set of changes from, in effect, diff PB PL
with those from diff PB PR
.)
The most obvious case occurs for one side (let's choose the left side first) to say:
unchanged context
-changed line
+replacement 1
more unchanged context
and the other to say:
unchanged context
-changed line
+replacement 2
more unchanged context
Here one or more lines are deleted and in their stead, one or more lines are inserted, but the inserted lines do not match. In this case the merge
conflict style presents this as:
unchanged context
<<<<<<< left-label
replacement 1
=======
replacement 2
>>>>>>> right-label
more unchanged context
The diff3
context style presents this instead as:
unchanged context
<<<<<<< left-label
replacement 1
||||||| merged common ancestors
changed line
=======
replacement 2
>>>>>>> right-label
more unchanged context
If we merely add text, but add different text, at the same line, we also get a merge conflict. Again, the added text from each side shows up in the conflict markers. (If we add the same text to both sides—either as new text, or as replacement text for a changed line, Git takes one copy of this addition and there is no conflict.)
If one but not both of the replacement lines is empty—i.e., if the left or right side diff reads:
unchanged context
-changed line
more unchanged context
then one but not both of the replacement lines in the merge
or diff3
style marked-up file is missing. (If both diffs simply delete the original lines, there is no conflict: Git takes one deletion.)
Similarly, if one side adds a line above or below a line that the other side deletes, you get a conflict. This time the conflict shows that the side that retained-and-then-added a has all the lines, and other other side has no lines. For instance:
some merge conflict.
Line that will conflict.
+add line below it
Rest of the
vs:
some merge conflict.
-Line that will conflict.
Rest of the
(and the same occurs if you add the line above instead of below).
This is where the diff3
conflict style is very helpful. Here's the entire merged file for the one of these cases:
We need a base file
in which to make
some merge conflict.
<<<<<<< HEAD
||||||| merged common ancestors
Line that will conflict.
=======
Change the line that will conflict.
>>>>>>> b2
Rest of the
base file for the
merge conflict example.
Note that it's now obvious—or at least, less mysterious—that there was a line that read Line that will conflict.
in the base version, which I deleted entirely from the left side HEAD
version and replaced with a different line in the right side b2
version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With