As far as Git is concerned, that is the correct merge result.
Git's git merge
works by comparing two branch tips to a common merge base. That is, if you draw the commit graph, and it looks like this:
o--o--L <-- branch1
/
...--o--o--*
\
o--o--R <-- branch2
then the result of merging branch1
(commit L
) with branch2
(commit R
) will be computed by comparing the contents of commit *
, the common point from which both branches derive, to the contents of commit L
and to the contents of commit R
.
Whatever changed from *
to L
happened in the left side. Whatever changed from *
to R
happened in the right side. Git combines these two "things that happened", applies those combined changes to the contents of *
, and if all goes well, makes a new commit from the result.
But you are using --allow-unrelated-histories
. This is required only when there is no common commit: we draw the history of L
and R
and find that they never diverged from a common point. For instance, we might get:
A--o--o--...--L <-- branch1
B--o--...--o--R <-- branch2
where A
and B
are both root commits. So what should Git use as the common starting point, to figure out what changed since then?
One can argue for various starting-points of one's choice, but Git's answer is: Use an empty commit. That is, with --allow-unrelated-histories
, Git pretends there's a common commit that has no files at all:
A--o--o--...--L <-- branch1
/
*
\
B--o--...--o--R <-- branch2
Comparing commit *
, which is empty, to commit L
, Git finds that every file on branch1
is newly created in the form that it has in L
. Comparing *
to R
, Git finds that every file on branch2
is newly created in the form that it has in R
. Wherever those files match, everything is fine: we just take that file. Wherever L
has a file that R
doesn't or vice versa, everything is fine: we just take that file. Wherever both L
and R
have a file, but their contents differ, we have a merge conflict.
Since you used -X theirs
, Git resolves each conflict by taking "their" changes ("theirs" being the commit you named, rather than the one you were on as HEAD
) since the empty merge-base commit.
If you want Git to pretend that some other commit is the merge base, you can use git replace --graft
to insert some fake parent linkage. For instance, suppose you want to fake-tie the two together at arbitrarily-chosen commit C:
A--o--o--...--L <-- branch1
/
B--...--C--...--R <-- branch2
so that Git will compare C
to L
, and C
to R
. As far as git merge
itself goes, it doesn't matter which commit you choose in the A--...--L
chain; any commit will do (including A
and L
themselves), but note that Git will only "see" changes since the commit you pick (due to diffing C
-vs-L
, and C
-vs-R
). So, pick some appropriate commit in the chain and run:
git replace --graft <hash-ID-of-chosen-commit> <hash-ID-of-C>
and Git will now use the replacement (graft) commit instead of the chosen commit. A git merge
will now use the contents of C
as the common source. (You can delete the graft as soon as git merge
exits, even if it exits with a merge conflict. Hence you can write a tiny script that inserts the graft, runs git merge
, and deletes the graft, to merge with arbitrarily-chosen ancestor. Just insert it as the replacement for L
as long as it's on the B--...--R
line, or insert it as the replacement for R
as long as it's on the A--...--L
line.)