This really should be a comment, but I do not have enough space in one, nor can I format things nicely.
The commit graph you drew has only one merge base for the two commits you have labeled 7
and perhaps not-labeled-at-all (the tip commit of release-1.0.x
). That merge base commit is the commit you have labeled 3
. But in your text, you say:
some files were merged back at 5
...
Yet there is no additional graph line connecting 5
anywhere: 5
looks like an ordinary, non-merge commit whose (single) parent is commit 4
. As a consequence, I am not sure if you have over-simplified your graph.
The easiest way to tell is to have Git tell you what Git thinks the merge base is, when your merge has issues. If you are currently on branch master
and are about to run git merge release-1.0.x
, you can see which commits Git believes are the merge bases (possibly plural) using:
git merge-base --all HEAD release-1.0.x
The output is some number of commit hash IDs—with luck, just one ID—that are the merge bases. (If you already did the merge, replace HEAD
with the hash ID if the commit that was the current commit before the merge. Likewise, if release-1.0.x has moved since the merge, replace that with the hash ID of the commit that was merged in.)
If there are multiple merge bases, you have a bit of a problem. Git's default, with the -s recursive
merge strategy, is to merge the merge bases. The result of this merge is a new "virtual commit", which is then treated as the merge base for the final merge. While you can (recursively) do what I am about to describe manually, it gets quite painful: among other things you may have to make real commits for each potential virtual commit. But with any luck1 there is just the one merge base, and maybe it is commit 3
after all.
In any case:
I'm getting tons of conflicts including both A.java
and B.java
...
To see what the merge will do, and why it thinks it has things to merge for these two files, run:
git diff -M <merge-base-commit> <tip-1> > /tmp/merge-input-1
git diff -M <merge-base-commit> <tip-2> > /tmp/merge-input-2
Here tip-1 is master
and tip-2 is release-1.0.x
. The merge base commit is, of course, the (single) hash ID you found above.
If the two files in question exist in all three commits, you won't be stumbling over any rename issues, so I will not even bring those up here.
1Well, it's technically not a matter of luck. Merge base ambiguity can only occur if you have criss-cross merges, and even then they need to have the same "distance", as it were. So it's relatively tough to produce multiple merge bases.
Finally, there's one last note. It might eventually be important, but is not actually the problem here. It regards this:
Versions are bumped inside pom.xml
s which had been added to .gitattributes
to be treated with ours strategy.
There is no way to obtain a true "ours" strategy in a .gitattributes
. (There isn't even an "ours" marker.) I am guessing here that you mean you wrote a merge driver that uses, in essence, git checkout --ours
when there is a conflict. It's also possible that you wrote a merge driver that uses git merge-file --ours
on the three inputs.
These are all subtly different.
The first thing to know is that your merge driver is only run if Git sees a conflict.
When you do the two git diff -M
commands above, you can see which files have changes that require combining. If, when compared to merge base B, both branch tips have a different pom.xml
, then there is something to merge. That is, suppose that in diff #1 we see:
-this
+that
and in diff #2 we see:
-another
+another
These need combining, so Git will invoke your merge driver. (More precisely, Git checks three hash IDs: hash of base file, hash of file in tip#1, and hash of file in tip#2. If all three differ, Git invokes your merge driver.)
On the other hand, perhaps only tip#1 or only tip#2 changes the file (so that it's the same in the base as in the other tip). In this case, Git takes whichever one has a changed file, without running your driver. For a file like pom.xml
this is probably, usually, what you want! (With version numbering, it probably isn't quite what you want, but perhaps you have another step that handles that.)
If Git does invoke your driver, then it is of course up to your driver. This is where "keep ours" vs "git merge-file --ours" differs. The former is more like the -s ours
strategy: it ignores the base and other/theirs file entirely. The latter uses the "take both changes, but in case of conflict, favor ours" resolution method, and is like the -X ours
extended-strategy-option.