# HG changeset patch # User Siddharth Agarwal # Date 1454105969 28800 # Node ID 6bce6d925e4569a7b57620d33dc3480d13b3cca1 # Parent f0d3c57943801a011d7ec7b8a4b3b8947b004a7f 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. diff -r f0d3c5794380 -r 6bce6d925e45 mercurial/merge.py --- a/mercurial/merge.py Wed Jan 27 16:16:38 2016 -0600 +++ b/mercurial/merge.py Fri Jan 29 14:19:29 2016 -0800 @@ -1464,6 +1464,34 @@ actionbyfile, diverge, renamedelete = calculateupdates( repo, wc, p2, pas, branchmerge, force, mergeancestor, followcopies, matcher=matcher) + + # Prompt and create actions. Most of this is in the resolve phase + # already, but we can't handle .hgsubstate in filemerge or + # subrepo.submerge yet so we have to keep prompting for it. + if '.hgsubstate' in actionbyfile: + f = '.hgsubstate' + m, args, msg = actionbyfile[f] + if m == 'cd': + if repo.ui.promptchoice( + _("local changed %s which remote deleted\n" + "use (c)hanged version or (d)elete?" + "$$ &Changed $$ &Delete") % f, 0): + actionbyfile[f] = ('r', None, "prompt delete") + elif f in p1: + actionbyfile[f] = ('am', None, "prompt keep") + else: + actionbyfile[f] = ('a', None, "prompt keep") + elif m == 'dc': + f1, f2, fa, move, anc = args + flags = p2[f2].flags() + if repo.ui.promptchoice( + _("remote changed %s which local deleted\n" + "use (c)hanged version or leave (d)eleted?" + "$$ &Changed $$ &Deleted") % f, 0) == 0: + actionbyfile[f] = ('g', (flags, False), "prompt recreating") + else: + del actionbyfile[f] + # Convert to dictionary-of-lists format actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split()) for f, (m, args, msg) in actionbyfile.iteritems(): @@ -1479,33 +1507,6 @@ else: _checkcollision(repo, wc.manifest(), actions) - # Prompt and create actions. Most of this is in the resolve phase - # already, but we can't handle .hgsubstate in filemerge or - # subrepo.submerge yet so we have to keep prompting for it. - for f, args, msg in sorted(actions['cd']): - if f != '.hgsubstate': - continue - if repo.ui.promptchoice( - _("local changed %s which remote deleted\n" - "use (c)hanged version or (d)elete?" - "$$ &Changed $$ &Delete") % f, 0): - actions['r'].append((f, None, "prompt delete")) - elif f in p1: - actions['am'].append((f, None, "prompt keep")) - else: - actions['a'].append((f, None, "prompt keep")) - - for f, args, msg in sorted(actions['dc']): - if f != '.hgsubstate': - continue - f1, f2, fa, move, anc = args - flags = p2[f2].flags() - if repo.ui.promptchoice( - _("remote changed %s which local deleted\n" - "use (c)hanged version or leave (d)eleted?" - "$$ &Changed $$ &Deleted") % f, 0) == 0: - actions['g'].append((f, (flags, False), "prompt recreating")) - # divergent renames for f, fl in sorted(diverge.iteritems()): repo.ui.warn(_("note: possible conflict - %s was renamed "