comparison 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
comparison
equal deleted inserted replaced
30855:72c36a2be2d6 30856:41f6af50c0d8
995 fbids[f] = {m: [a]} 995 fbids[f] = {m: [a]}
996 996
997 # Pick the best bid for each file 997 # Pick the best bid for each file
998 repo.ui.note(_('\nauction for merging merge bids\n')) 998 repo.ui.note(_('\nauction for merging merge bids\n'))
999 actions = {} 999 actions = {}
1000 dms = [] # filenames that have dm actions
1000 for f, bids in sorted(fbids.items()): 1001 for f, bids in sorted(fbids.items()):
1001 # bids is a mapping from action method to list af actions 1002 # bids is a mapping from action method to list af actions
1002 # Consensus? 1003 # Consensus?
1003 if len(bids) == 1: # all bids are the same kind of method 1004 if len(bids) == 1: # all bids are the same kind of method
1004 m, l = bids.items()[0] 1005 m, l = bids.items()[0]
1005 if all(a == l[0] for a in l[1:]): # len(bids) is > 1 1006 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1006 repo.ui.note(_(" %s: consensus for %s\n") % (f, m)) 1007 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1007 actions[f] = l[0] 1008 actions[f] = l[0]
1009 if m == 'dm':
1010 dms.append(f)
1008 continue 1011 continue
1009 # If keep is an option, just do it. 1012 # If keep is an option, just do it.
1010 if 'k' in bids: 1013 if 'k' in bids:
1011 repo.ui.note(_(" %s: picking 'keep' action\n") % f) 1014 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1012 actions[f] = bids['k'][0] 1015 actions[f] = bids['k'][0]
1027 # Pick random action. TODO: Instead, prompt user when resolving 1030 # Pick random action. TODO: Instead, prompt user when resolving
1028 m, l = bids.items()[0] 1031 m, l = bids.items()[0]
1029 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') % 1032 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1030 (f, m)) 1033 (f, m))
1031 actions[f] = l[0] 1034 actions[f] = l[0]
1035 if m == 'dm':
1036 dms.append(f)
1032 continue 1037 continue
1038 # Work around 'dm' that can cause multiple actions for the same file
1039 for f in dms:
1040 dm, (f0, flags), msg = actions[f]
1041 assert dm == 'dm', dm
1042 m, args, msg = actions[f0]
1043 if m == 'r':
1044 # We have one bid for removing a file and another for moving it.
1045 # These two could be merged as first move and then delete ...
1046 # but instead drop moving and just delete.
1047 del actions[f]
1033 repo.ui.note(_('end of auction\n\n')) 1048 repo.ui.note(_('end of auction\n\n'))
1034 1049
1035 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions) 1050 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1036 1051
1037 if wctx.rev() is None: 1052 if wctx.rev() is None: