Mercurial > public > mercurial-scm > hg
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) |