2

I've renamed a directory and merged it into my main branch, when merging another branch, git recognises the same files in the renamed directory as new files.

I get the merge conflict 'added by them' for the same files in that directory when merging in another's branch:

added by them: theirDir/same_file_name.xxx

I've renamed the directory in their branch to match and continue to get the 'added by them' conflict.

When I try to checkout --ours I get

$ git checkout dir/same_file_name.xxx --ours

error: path 'dir/same_file_name.xxx' does not have our version

At this point deleting this file actually, deletes the file after the merge completes.

How can I resolve this conflict?

--- m ----\ ------------  m1 --------------- mx ------- *!*
     \     \            /   \                /         /  
      \     dir-rename-/     file_revisions-/         /
       \                                             /
        f2 ---------- file added by them conflict --/

Multiple file revisions from multiple branches have occurred before the merge conflict.

David Sopko
  • 5,263
  • 2
  • 38
  • 42
  • Were there changes to the files within the directory in addition to the directory being renamed? – Dave Jan 23 '17 at 16:52
  • No, the files have not been changed in the branch being merged. Changes may have been made to some of those files since the directory was renamed. – David Sopko Jan 23 '17 at 16:56
  • I'm a little confused about which file was where when... Do you mean you backed out of the merge, renamed the directory and committed that to their branch, and then attempted the merge again? – Mark Adelsberger Jan 23 '17 at 17:01
  • I can't reproduce based on your description: a repo with master + two branches; rename a dir on branch-1, make some changes to files in 'dir', then merge to master; on branch-2, make changes to files not in 'dir', then merge to master. – Dave Jan 23 '17 at 17:32
  • Multiple file revisions from multiple branches have occurred before the merge conflict. I updated the question description. – David Sopko Jan 23 '17 at 17:59
  • I'm not entirely sure what your `m1` and `mx` represent, but it's worth noting that Git doesn't even *look* at any of the intermediate commits when doing a merge. It looks at the common base commit, `m` in your diagram, and diffs that against each branch tip (two separate `git diff`-s) to figure out "what you did" vs "what they did". – torek Jan 23 '17 at 19:11
  • @torek diff to compare the tip of master to what and compare the top of the feature branch to what? – David Sopko Jan 23 '17 at 20:17
  • 2
    Each diff starts from the merge base of the two tip commits. To find the merge base, use `git merge-base --all `, e.g., `git merge-base --all master branch`. If this prints multiple merge base commits the next step depends on your chosen merge strategy, but usually you will get just one commit ID. You can then `git diff -M ` and repeat for `` to see what Git will see. Note that you can tweak the `-M` rename-detection threshold, both in `git diff` and in `git merge`. – torek Jan 23 '17 at 21:07
  • What version of git are you running? – Dave Jan 23 '17 at 21:29
  • @Dave I'm running version 1.9.5.msysgit.1 – David Sopko Jan 23 '17 at 22:13
  • I was curious about your git version because of this question: http://stackoverflow.com/q/3021649 (there have been changes to the way git informs the user about work tree status). – Dave Jan 23 '17 at 22:25

1 Answers1

1

Instead of using msysgit 1.9.5, try the latest Git for Windows:

Since Git 2.18, git status does a better job detecting renamed (of files and folders)


With Git 2.33 (Q3 2021), it will go even further, managing content based rename detection and directory rename detection.

But only with the new merge strategy ORT ("Ostensibly Recursive's Twin").

See commit 3585d0e, commit a492d53, commit 806f832 (30 Jun 2021) by Elijah Newren (newren).
(Merged by Junio C Hamano -- gitster -- in commit d3b88be, 16 Jul 2021)

merge-recursive: handle rename-to-self case

Reported-by: Anders Kaseorg
Signed-off-by: Elijah Newren

Directory rename detection can cause transitive renames, e.g. if the two different sides of history each do one half of:

A/file -> B/file
B/     -> C/

then directory rename detection transitively renames to give us

A/file -> C/file

However, when C/ == A/, note that this gives us

A/file -> A/file.

merge-recursive assumed that any rename D -> E would have D != E.
While that is almost always true, the above is a special case where it is not.
So we cannot do things like delete the rename source, we cannot assume that a file existing at path E implies a rename/add conflict and we have to be careful about what stages end up in the output.

This change feels a bit hackish.
It took me surprisingly many hours to find, and given merge-recursive's design causing it to attempt to enumerate all combinations of edge and corner cases with special code for each combination, I'm worried there are other similar fixes needed elsewhere if we can just come up with the right special testcase.
Perhaps an audit would rule it out, but I have not the energy.
merge-recursive deserves to die, and since it is on its way out anyway, fixing this particular bug narrowly will have to be good enough.


A corner case bug in the ort merge strategy has been corrected with Git 2.35 (Q1 2022).

See commit d30126c (28 Dec 2021) by Elijah Newren (newren).
(Merged by Junio C Hamano -- gitster -- in commit 2c54104, 10 Jan 2022)

merge-ort: fix bug with renormalization and rename/delete conflicts

Reported-by: Ralf Thielow
Signed-off-by: Elijah Newren
Reviewed-by: Derrick Stolee

Ever since commit a492d53 (merge-ort: ensure we consult df_conflict and path_conflicts, 2021-06-30, Git v2.33.0-rc0 -- merge listed in batch #5) ("merge-ort: ensure we consult df_conflict and path_conflicts", 2021-06-30), when renormalization is active AND a file is involved in a rename/delete conflict BUT the file is unmodified (either before or after renormalization), merge-ort was running into an assertion failure.

Prior to that commit (or if assertions were compiled out), merge-ort would mis-merge instead, ignoring the rename/delete conflict and just deleting the file.

Remove the assertions, fix the code appropriately, leave some good comments in the code, and add a testcase for this situation.


Git 2.38 (Q3 2022) fixes a long-standing corner case bug around directory renames in the merge-ort strategy.

See commit 751e165, commit 3ffbe5a, commit 6dd1f0e, commit 51e41e4, commit 0565cee (05 Jul 2022) by Elijah Newren (newren).
(Merged by Junio C Hamano -- gitster -- in commit e3349f2, 18 Jul 2022)

merge-ort: fix issue with dual rename and add/add conflict

Signed-off-by: Elijah Newren

There is code in both merge-recursive and merge-ort for avoiding doubly transitive renames (i.e.
one side renames directory A/ -> B/, and the other side renames directory B/ -> C/), because this combination would otherwise make a mess for new files added to A/ on the first side and wondering which directory they end up in -- especially if there were even more renames such as the first side renaming C/ -> D/.

In such cases, it just turns "off" directory rename detection for the higher order transitive cases.

Another way to look at this is that if the source name involved in a directory rename on one side is the target name of a directory rename operation for a file from the other side, then we avoid the doubly transitive rename.

(More concretely, if a directory rename on side D wants to rename a file on side E from OLD_NAME -> NEW_NAME, and side D already had a file named NEW_NAME, and a directory rename on side E wants to rename side D's NEW_NAME -> NEWER_NAME, then we turn off the directory rename detection for NEW_NAME to prevent the NEW_NAME -> NEWER_NAME rename, and instead end up with an add/add conflict on NEW_NAME.)

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250