diff mercurial/merge.py @ 30856:41f6af50c0d8 stable

merge: fix crash on criss cross merge with dir move and delete (issue5020) Work around that 'dm' in the data model only can have one operation for the target file, but still can have multiple and conflicting operations on the source file where the other operation is a 'rm'. The move would thus fail with 'abort: No such file or directory'. In this case it is "obvious" that the file should be removed, either before or after moving it. We thus keep the 'rm' of the source file but drop the 'dm'. This is not a pretty fix but quite "obviously" safe (famous last words...) as it only touches a rare code path that used to crash. It is possible that it would be better to swap the files for 'dm' as suggested on https://bz.mercurial-scm.org/show_bug.cgi?id=5020#c13 but it is not entirely obvious that it not just would create conflicts on the other file. That can be revisited later.
author Mads Kiilerich <mads@kiilerich.com>
date Tue, 31 Jan 2017 03:25:59 +0100
parents 43a9e02a7b7f
children 086c37652735
line wrap: on
line diff
--- a/mercurial/merge.py	Tue Jan 31 03:20:07 2017 +0100
+++ b/mercurial/merge.py	Tue Jan 31 03:25:59 2017 +0100
@@ -997,6 +997,7 @@
         # Pick the best bid for each file
         repo.ui.note(_('\nauction for merging merge bids\n'))
         actions = {}
+        dms = [] # filenames that have dm actions
         for f, bids in sorted(fbids.items()):
             # bids is a mapping from action method to list af actions
             # Consensus?
@@ -1005,6 +1006,8 @@
                 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
                     repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
                     actions[f] = l[0]
+                    if m == 'dm':
+                        dms.append(f)
                     continue
             # If keep is an option, just do it.
             if 'k' in bids:
@@ -1029,7 +1032,19 @@
             repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
                          (f, m))
             actions[f] = l[0]
+            if m == 'dm':
+                dms.append(f)
             continue
+        # Work around 'dm' that can cause multiple actions for the same file
+        for f in dms:
+            dm, (f0, flags), msg = actions[f]
+            assert dm == 'dm', dm
+            m, args, msg = actions[f0]
+            if m == 'r':
+                # We have one bid for removing a file and another for moving it.
+                # These two could be merged as first move and then delete ...
+                # but instead drop moving and just delete.
+                del actions[f]
         repo.ui.note(_('end of auction\n\n'))
 
     _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)