0

Considering the following git network and flow:

feature-foo        _._._2_._._
                  /           \
master      __1__/__._.3____._._\___4__5_________7_  (now: 1.1.0-SNAPSHOT)
                        \         
release-1.0.x            \___.__._._.__6__._._.____  (now: 1.0.1-SNAPSHOT)

with the following actions over times:

  1. bump master's version to 1.0.0-SNAPSHOT
  2. delete A.java in feature-foo branch
  3. branch off release-1.0.x, bump master's version to 1.1.0-SNAPSHOT for next release development
  4. B.java is modified to B'.java only on master
  5. manually merge release-1.0.x back to master (git merge not through GitLab MR)
  6. bump release-1.0.x's version to 1.0.0 and tag it and bump it again to 1.0.1-SNAPSHOT
  7. failed second attempt to merge release-1.0.x back to master with recent changes on release branch

and

  • 3 and 6 were done via a bash script on /tmp with full git clone
  • 5 and 7 were done manually on separate /path/to/tmp with full git clone
  • feature-foo was merged back to master (accepting MR)
  • master at 1 had A.java
  • release-1.0.x still has A.java (which is fine)
  • release-1.0.x still has B.java (which is fine)
  • some files were merged back at 5 excluding A.java and B.java

For both step 5 and 7 I did

git clone ...
git checkout release-1.0.x
git checkout master
git merge --no-ff --no-commit release-1.0.x

successfully on 5:

git add .
git commit
git push

And strangely now on second attempt, I'm getting tons of conflicts including both A.java (it tries to bring over to master) and B.java (it tries to merge the outdated from release branch into master and produces conflict).

My questions are:

  • Am I missing something (obvious) here?
  • Is the process even correct to merge long live release branch back to master (e.g. twice per month)?
  • How come I'm getting conflicts now and not on the first attempt?

Edit

Versions are bumped inside pom.xmls which had been added to .gitattributes to be treated with ours strategy.

Khosrow
  • 556
  • 1
  • 6
  • 16
  • `--no-commit` aborts the merge right before it's committed, which means that if you don't manually call `git commit` afterwards, the merge won't happen. Instead you end up with lots of uncommitted changes made by the merge process. These will cause all manner of problems if you just leave them. Did you do anything else after 5 that you didn't mention? – oyvind May 27 '17 at 02:55
  • @oyvind yes only on 5 I did indeed, I didn't reach the point on 7 to commit. I'll update the question. – Khosrow May 27 '17 at 02:59
  • Or rather, did 6 happen on the same clone as 5? Or was 6 done somewhere else? – oyvind May 27 '17 at 02:59
  • @oyvind updated the question. 3 and 6 were pure clones on individual folders in `/tmp` – Khosrow May 27 '17 at 03:04

2 Answers2

1

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.xmls 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.

torek
  • 448,244
  • 59
  • 642
  • 775
0

After 6, both master and release have bumped their version. Your don't mention exactly what bumping versions entails, but I'm assuming that the same lines in the same files have been edited into both branches, which will cause conflicts. As to why a.java and b.java are suddenly conflicted, my guess is that they (or their path) were edited during 6.

oyvind
  • 1,429
  • 3
  • 14
  • 24
  • Check the "edit". Versions bump were in pom.xml and weren't part of the conflicts list. Actual content of A.java and B.java have conflicts. And that's the strange part, nothing has touched A and B except 2 and 4. – Khosrow May 27 '17 at 03:23
  • Were there additional commits on release between 5 and 7? – oyvind May 27 '17 at 03:31
  • yes, but none of them touched neither of A or B or any of the poms – Khosrow May 27 '17 at 03:32
  • I'm almost out of ideas :-P. Are you sure 5 was successful? Something weird could have happened since you were doing the commit manually. – oyvind May 27 '17 at 03:37
  • Tell me about it!! As far as I checked the history on both master and release branches and even checkout individual in-between commits in detached mode nothing seems to have gone wrong on 5 or 6. – Khosrow May 27 '17 at 03:47
  • Were the paths of a and b modified between 5 and 7? Or, perhaps there is something else going on at 7? Something you missed or misread? – oyvind May 27 '17 at 03:50
  • the paths are definitely identical, I'll try to examine the history once more to see if I have missed anything. – Khosrow May 27 '17 at 04:07