comparison mercurial/merge.py @ 27951:6bce6d925e45 stable

merge: don't try to merge subrepos twice (issue4988) In my patch series ending with rev 25e4b2f000c5 I switched most change/delete conflicts to be handled at the resolve layer. .hgsubstate was the one file that we weren't able to handle, so we kept the old code path around for it. The old code path added .hgsubstate to one of the other lists as the user specifies, including possibly the 'g' list. Now since we did this check after converting the actions from being keyed by file to being keyed by action type, there was nothing that actually removed .hgsubstate from the 'cd' or 'dc' lists. This meant that the file would eventually make its way into the 'mergeactions' list, now freshly augmented with 'cd' and 'dc' actions. We call subrepo.submerge for both 'g' actions and merge actions. This means that if the resolution to an .hgsubstate change/delete conflict was to add it to the 'g' list, subrepo.submerge would be called twice. It turns out that this doesn't cause any adverse effects on Linux due to caching, but apparently breaks on other operating systems including Windows. The fix here moves this to before we convert the actions over. This ensures that it .hgsubstate doesn't make its way into multiple lists. The real fix here is going to be: (1) move .hgsubstate conflict resolution into the resolve layer, and (2) use a real data structure for the actions rather than shuffling data around between lists and dictionaries: we need a hash (or prefix-based) index by file and a list index by action type. There's a very tiny behavior change here: collision detection on case-insensitive systems will happen after this is resolved, not before. I think this is the right change -- .hgsubstate could theoretically collide with other files -- but in any case it makes no practical difference. Thanks to Yuya Nishihara for investigating this.
author Siddharth Agarwal <sid0@fb.com>
date Fri, 29 Jan 2016 14:19:29 -0800
parents a33c1c9e769c
children 4a25e91fa55d
comparison
equal deleted inserted replaced
27950:f0d3c5794380 27951:6bce6d925e45
1462 1462
1463 ### calculate phase 1463 ### calculate phase
1464 actionbyfile, diverge, renamedelete = calculateupdates( 1464 actionbyfile, diverge, renamedelete = calculateupdates(
1465 repo, wc, p2, pas, branchmerge, force, mergeancestor, 1465 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1466 followcopies, matcher=matcher) 1466 followcopies, matcher=matcher)
1467
1468 # Prompt and create actions. Most of this is in the resolve phase
1469 # already, but we can't handle .hgsubstate in filemerge or
1470 # subrepo.submerge yet so we have to keep prompting for it.
1471 if '.hgsubstate' in actionbyfile:
1472 f = '.hgsubstate'
1473 m, args, msg = actionbyfile[f]
1474 if m == 'cd':
1475 if repo.ui.promptchoice(
1476 _("local changed %s which remote deleted\n"
1477 "use (c)hanged version or (d)elete?"
1478 "$$ &Changed $$ &Delete") % f, 0):
1479 actionbyfile[f] = ('r', None, "prompt delete")
1480 elif f in p1:
1481 actionbyfile[f] = ('am', None, "prompt keep")
1482 else:
1483 actionbyfile[f] = ('a', None, "prompt keep")
1484 elif m == 'dc':
1485 f1, f2, fa, move, anc = args
1486 flags = p2[f2].flags()
1487 if repo.ui.promptchoice(
1488 _("remote changed %s which local deleted\n"
1489 "use (c)hanged version or leave (d)eleted?"
1490 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1491 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1492 else:
1493 del actionbyfile[f]
1494
1467 # Convert to dictionary-of-lists format 1495 # Convert to dictionary-of-lists format
1468 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split()) 1496 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1469 for f, (m, args, msg) in actionbyfile.iteritems(): 1497 for f, (m, args, msg) in actionbyfile.iteritems():
1470 if m not in actions: 1498 if m not in actions:
1471 actions[m] = [] 1499 actions[m] = []
1476 if (not branchmerge and 1504 if (not branchmerge and
1477 (force or not wc.dirty(missing=True, branch=False))): 1505 (force or not wc.dirty(missing=True, branch=False))):
1478 _checkcollision(repo, p2.manifest(), None) 1506 _checkcollision(repo, p2.manifest(), None)
1479 else: 1507 else:
1480 _checkcollision(repo, wc.manifest(), actions) 1508 _checkcollision(repo, wc.manifest(), actions)
1481
1482 # Prompt and create actions. Most of this is in the resolve phase
1483 # already, but we can't handle .hgsubstate in filemerge or
1484 # subrepo.submerge yet so we have to keep prompting for it.
1485 for f, args, msg in sorted(actions['cd']):
1486 if f != '.hgsubstate':
1487 continue
1488 if repo.ui.promptchoice(
1489 _("local changed %s which remote deleted\n"
1490 "use (c)hanged version or (d)elete?"
1491 "$$ &Changed $$ &Delete") % f, 0):
1492 actions['r'].append((f, None, "prompt delete"))
1493 elif f in p1:
1494 actions['am'].append((f, None, "prompt keep"))
1495 else:
1496 actions['a'].append((f, None, "prompt keep"))
1497
1498 for f, args, msg in sorted(actions['dc']):
1499 if f != '.hgsubstate':
1500 continue
1501 f1, f2, fa, move, anc = args
1502 flags = p2[f2].flags()
1503 if repo.ui.promptchoice(
1504 _("remote changed %s which local deleted\n"
1505 "use (c)hanged version or leave (d)eleted?"
1506 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1507 actions['g'].append((f, (flags, False), "prompt recreating"))
1508 1509
1509 # divergent renames 1510 # divergent renames
1510 for f, fl in sorted(diverge.iteritems()): 1511 for f, fl in sorted(diverge.iteritems()):
1511 repo.ui.warn(_("note: possible conflict - %s was renamed " 1512 repo.ui.warn(_("note: possible conflict - %s was renamed "
1512 "multiple times to:\n") % f) 1513 "multiple times to:\n") % f)