mercurial/merge.py
changeset 21545 43eecb4e23f8
parent 21524 47b97d9af27e
child 21551 bde505f47141
--- a/mercurial/merge.py	Fri May 23 13:10:31 2014 -0700
+++ b/mercurial/merge.py	Fri Feb 28 02:25:58 2014 +0100
@@ -331,62 +331,44 @@
     as removed.
     """
 
-    actions = []
-    state = branchmerge and 'r' or 'f'
+    ractions = []
+    factions = xactions = []
+    if branchmerge:
+        xactions = ractions
     for f in wctx.deleted():
         if f not in mctx:
-            actions.append((f, state, None, "forget deleted"))
+            xactions.append((f, None, "forget deleted"))
 
     if not branchmerge:
         for f in wctx.removed():
             if f not in mctx:
-                actions.append((f, "f", None, "forget removed"))
+                factions.append((f, None, "forget removed"))
 
-    return actions
+    return ractions, factions
 
 def _checkcollision(repo, wmf, actions):
     # build provisional merged manifest up
     pmmf = set(wmf)
 
-    def addop(f, args):
-        pmmf.add(f)
-    def removeop(f, args):
-        pmmf.discard(f)
-    def nop(f, args):
-        pass
-
-    def renamemoveop(f, args):
-        f2, flags = args
-        pmmf.discard(f2)
-        pmmf.add(f)
-    def renamegetop(f, args):
-        f2, flags = args
-        pmmf.add(f)
-    def mergeop(f, args):
-        f1, f2, fa, move, anc = args
-        if move:
-            pmmf.discard(f1)
-        pmmf.add(f)
-
-    opmap = {
-        "a": addop,
-        "dm": renamemoveop,
-        "dg": renamegetop,
-        "dr": nop,
-        "e": nop,
-        "k": nop,
-        "f": addop, # untracked file should be kept in working directory
-        "g": addop,
-        "m": mergeop,
-        "r": removeop,
-        "rd": nop,
-        "cd": addop,
-        "dc": addop,
-    }
-    for f, m, args, msg in actions:
-        op = opmap.get(m)
-        assert op, m
-        op(f, args)
+    if actions:
+        # k, dr, e and rd are no-op
+        for m in 'a', 'f', 'g', 'cd', 'dc':
+            for f, args, msg in actions[m]:
+                pmmf.add(f)
+        for f, args, msg in actions['r']:
+            pmmf.discard(f)
+        for f, args, msg in actions['dm']:
+            f2, flags = args
+            pmmf.discard(f2)
+            pmmf.add(f)
+        for f, args, msg in actions['dg']:
+            f2, flags = args
+            pmmf.add(f)
+        for f, args, msg in actions['m']:
+            f1, f2, fa, move, anc = args
+            if move:
+                pmmf.discard(f1)
+            pmmf.add(f)
 
     # check case-folding collision in provisional merged manifest
     foldmap = {}
@@ -407,7 +389,8 @@
     acceptremote = accept the incoming changes without prompting
     """
 
-    actions, copy, movewithdir = [], {}, {}
+    actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
+    copy, movewithdir = {}, {}
 
     # manifests fetched in order are going to be faster, so prime the caches
     [x.manifest() for x in
@@ -417,9 +400,9 @@
         ret = copies.mergecopies(repo, wctx, p2, pa)
         copy, movewithdir, diverge, renamedelete = ret
         for of, fl in diverge.iteritems():
-            actions.append((of, "dr", (fl,), "divergent renames"))
+            actions['dr'].append((of, (fl,), "divergent renames"))
         for of, fl in renamedelete.iteritems():
-            actions.append((of, "rd", (fl,), "rename and delete"))
+            actions['rd'].append((of, (fl,), "rename and delete"))
 
     repo.ui.note(_("resolving manifests\n"))
     repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
@@ -471,50 +454,50 @@
             fla = ma.flags(fa)
             nol = 'l' not in fl1 + fl2 + fla
             if n2 == a and fl2 == fla:
-                actions.append((f, "k", (), "keep")) # remote unchanged
+                actions['k'].append((f, (), "keep")) # remote unchanged
             elif n1 == a and fl1 == fla: # local unchanged - use remote
                 if n1 == n2: # optimization: keep local content
-                    actions.append((f, "e", (fl2,), "update permissions"))
+                    actions['e'].append((f, (fl2,), "update permissions"))
                 else:
-                    actions.append((f, "g", (fl2,), "remote is newer"))
+                    actions['g'].append((f, (fl2,), "remote is newer"))
             elif nol and n2 == a: # remote only changed 'x'
-                actions.append((f, "e", (fl2,), "update permissions"))
+                actions['e'].append((f, (fl2,), "update permissions"))
             elif nol and n1 == a: # local only changed 'x'
-                actions.append((f, "g", (fl1,), "remote is newer"))
+                actions['g'].append((f, (fl1,), "remote is newer"))
             else: # both changed something
-                actions.append((f, "m", (f, f, fa, False, pa.node()),
+                actions['m'].append((f, (f, f, fa, False, pa.node()),
                                "versions differ"))
         elif f in copied: # files we'll deal with on m2 side
             pass
         elif n1 and f in movewithdir: # directory rename, move local
             f2 = movewithdir[f]
-            actions.append((f2, "dm", (f, fl1),
+            actions['dm'].append((f2, (f, fl1),
                             "remote directory rename - move from " + f))
         elif n1 and f in copy:
             f2 = copy[f]
-            actions.append((f, "m", (f, f2, f2, False, pa.node()),
+            actions['m'].append((f, (f, f2, f2, False, pa.node()),
                             "local copied/moved from " + f2))
         elif n1 and f in ma: # clean, a different, no remote
             if n1 != ma[f]:
                 if acceptremote:
-                    actions.append((f, "r", None, "remote delete"))
+                    actions['r'].append((f, None, "remote delete"))
                 else:
-                    actions.append((f, "cd", None, "prompt changed/deleted"))
+                    actions['cd'].append((f, None, "prompt changed/deleted"))
             elif n1[20:] == "a": # added, no remote
-                actions.append((f, "f", None, "remote deleted"))
+                actions['f'].append((f, None, "remote deleted"))
             else:
-                actions.append((f, "r", None, "other deleted"))
+                actions['r'].append((f, None, "other deleted"))
         elif n2 and f in movewithdir:
             f2 = movewithdir[f]
-            actions.append((f2, "dg", (f, fl2),
+            actions['dg'].append((f2, (f, fl2),
                             "local directory rename - get from " + f))
         elif n2 and f in copy:
             f2 = copy[f]
             if f2 in m2:
-                actions.append((f, "m", (f2, f, f2, False, pa.node()),
+                actions['m'].append((f, (f2, f, f2, False, pa.node()),
                                 "remote copied from " + f2))
             else:
-                actions.append((f, "m", (f2, f, f2, True, pa.node()),
+                actions['m'].append((f, (f2, f, f2, True, pa.node()),
                                 "remote moved from " + f2))
         elif n2 and f not in ma:
             # local unknown, remote created: the logic is described by the
@@ -530,17 +513,17 @@
             # Checking whether the files are different is expensive, so we
             # don't do that when we can avoid it.
             if force and not branchmerge:
-                actions.append((f, "g", (fl2,), "remote created"))
+                actions['g'].append((f, (fl2,), "remote created"))
             else:
                 different = _checkunknownfile(repo, wctx, p2, f)
                 if force and branchmerge and different:
                     # FIXME: This is wrong - f is not in ma ...
-                    actions.append((f, "m", (f, f, f, False, pa.node()),
+                    actions['m'].append((f, (f, f, f, False, pa.node()),
                                     "remote differs from untracked local"))
                 elif not force and different:
                     aborts.append((f, "ud"))
                 else:
-                    actions.append((f, "g", (fl2,), "remote created"))
+                    actions['g'].append((f, (fl2,), "remote created"))
         elif n2 and n2 != ma[f]:
             different = _checkunknownfile(repo, wctx, p2, f)
             if not force and different:
@@ -548,10 +531,10 @@
             else:
                 # if different: old untracked f may be overwritten and lost
                 if acceptremote:
-                    actions.append((f, "g", (m2.flags(f),),
+                    actions['g'].append((f, (m2.flags(f),),
                                    "remote recreating"))
                 else:
-                    actions.append((f, "dc", (m2.flags(f),),
+                    actions['dc'].append((f, (m2.flags(f),),
                                    "prompt deleted/changed"))
 
     for f, m in sorted(aborts):
@@ -566,18 +549,12 @@
         # check collision between files only in p2 for clean update
         if (not branchmerge and
             (force or not wctx.dirty(missing=True, branch=False))):
-            _checkcollision(repo, m2, [])
+            _checkcollision(repo, m2, None)
         else:
             _checkcollision(repo, m1, actions)
 
     return actions
 
-actionpriority = dict((m, p) for p, m in enumerate(
-    ['r', 'f', 'g', 'a', 'k', 'm', 'dm', 'dg', 'dr', 'cd', 'dc', 'rd', 'e']))
-
-def actionkey(a):
-    return actionpriority[a[1]], a
-
 def batchremove(repo, actions):
     """apply removes to the working directory
 
@@ -588,7 +565,7 @@
     wjoin = repo.wjoin
     audit = repo.wopener.audit
     i = 0
-    for f, m, args, msg in actions:
+    for f, args, msg in actions:
         repo.ui.debug(" %s: %s -> r\n" % (f, msg))
         if True:
             if verbose:
@@ -617,7 +594,7 @@
     fctx = mctx.filectx
     wwrite = repo.wwrite
     i = 0
-    for f, m, args, msg in actions:
+    for f, args, msg in actions:
         repo.ui.debug(" %s: %s -> g\n" % (f, msg))
         if True:
             if verbose:
@@ -644,12 +621,12 @@
     ms = mergestate(repo)
     ms.reset(wctx.p1().node(), mctx.node())
     moves = []
-    actions.sort(key=actionkey)
+    for m, l in actions.items():
+        l.sort()
 
     # prescan for merges
-    for a in actions:
-        f, m, args, msg = a
-        if m == "m": # merge
+    for f, args, msg in actions['m']:
+        if True:
             f1, f2, fa, move, anc = args
             if f == '.hgsubstate': # merged internally
                 continue
@@ -677,55 +654,50 @@
             audit(f)
             util.unlinkpath(repo.wjoin(f))
 
-    numupdates = len([a for a in actions if a[1] != 'k'])
-    workeractions = [a for a in actions if a[1] in 'gr']
-    updateactions = [a for a in workeractions if a[1] == 'g']
-    updated = len(updateactions)
-    removeactions = [a for a in workeractions if a[1] == 'r']
-    removed = len(removeactions)
-    actions = [a for a in actions if a[1] not in 'gr']
+    numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
 
-    hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
-    if hgsub and hgsub[0] == 'r':
+    if [a for a in actions['r'] if a[0] == '.hgsubstate']:
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
 
     # remove in parallel (must come first)
     z = 0
-    prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), removeactions)
+    prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
     for i, item in prog:
         z += i
         progress(_updating, z, item=item, total=numupdates, unit=_files)
+    removed = len(actions['r'])
 
     # get in parallel
-    prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), updateactions)
+    prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
     for i, item in prog:
         z += i
         progress(_updating, z, item=item, total=numupdates, unit=_files)
+    updated = len(actions['g'])
 
-    if hgsub and hgsub[0] == 'g':
+    if [a for a in actions['g'] if a[0] == '.hgsubstate']:
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
 
-    for f, m, args, msg in actions:
+    if True:
 
         # forget (manifest only, just log it) (must come first)
-        if m == "f":
+        for f, args, msg in actions['f']:
             repo.ui.debug(" %s: %s -> f\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
 
         # re-add (manifest only, just log it)
-        elif m == "a":
+        for f, args, msg in actions['a']:
             repo.ui.debug(" %s: %s -> a\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
 
         # keep (noop, just log it)
-        elif m == "k":
+        for f, args, msg in actions['k']:
             repo.ui.debug(" %s: %s -> k\n" % (f, msg))
             # no progress
 
         # merge
-        elif m == "m":
+        for f, args, msg in actions['m']:
             repo.ui.debug(" %s: %s -> m\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
@@ -745,7 +717,7 @@
                     merged += 1
 
         # directory rename, move local
-        elif m == "dm":
+        for f, args, msg in actions['dm']:
             repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
@@ -757,7 +729,7 @@
             updated += 1
 
         # local directory rename, get
-        elif m == "dg":
+        for f, args, msg in actions['dg']:
             repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
@@ -767,7 +739,7 @@
             updated += 1
 
         # divergent renames
-        elif m == "dr":
+        for f, args, msg in actions['dr']:
             repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
@@ -778,7 +750,7 @@
                 repo.ui.warn(" %s\n" % nf)
 
         # rename and delete
-        elif m == "rd":
+        for f, args, msg in actions['rd']:
             repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
@@ -789,7 +761,7 @@
                 repo.ui.warn(" %s\n" % nf)
 
         # exec
-        elif m == "e":
+        for f, args, msg in actions['e']:
             repo.ui.debug(" %s: %s -> e\n" % (f, msg))
             z += 1
             progress(_updating, z, item=f, total=numupdates, unit=_files)
@@ -818,132 +790,127 @@
             (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
 
         # Call for bids
-        fbids = {} # mapping filename to list af action bids
+        fbids = {} # mapping filename to bids (action method to list af actions)
         for ancestor in ancestors:
             repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
             actions = manifestmerge(repo, wctx, mctx, ancestor,
                                     branchmerge, force,
                                     partial, acceptremote, followcopies)
-            for a in sorted(actions, key=lambda a: (a[1], a)):
-                f, m, args, msg = a
-                repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
-                if f in fbids:
-                    fbids[f].append(a)
-                else:
-                    fbids[f] = [a]
+            for m, l in sorted(actions.items()):
+                for a in l:
+                    f, args, msg = a
+                    repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
+                    if f in fbids:
+                        d = fbids[f]
+                        if m in d:
+                            d[m].append(a)
+                        else:
+                            d[m] = [a]
+                    else:
+                        fbids[f] = {m: [a]}
 
         # Pick the best bid for each file
         repo.ui.note(_('\nauction for merging merge bids\n'))
-        actions = []
-        for f, bidsl in sorted(fbids.items()):
+        actions = dict((m, []) for m in actions.keys())
+        for f, bids in sorted(fbids.items()):
+            # bids is a mapping from action method to list af actions
             # Consensus?
-            a0 = bidsl[0]
-            if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1
-                repo.ui.note(" %s: consensus for %s\n" % (f, a0[1]))
-                actions.append(a0)
-                continue
-            # Group bids by kind of action
-            bids = {}
-            for a in bidsl:
-                m = a[1]
-                if m in bids:
-                    bids[m].append(a)
-                else:
-                    bids[m] = [a]
+            if len(bids) == 1: # all bids are the same kind of method
+                m, l = bids.items()[0]
+                if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
+                    repo.ui.note(" %s: consensus for %s\n" % (f, m))
+                    actions[m].append(l[0])
+                    continue
             # If keep is an option, just do it.
             if "k" in bids:
                 repo.ui.note(" %s: picking 'keep' action\n" % f)
-                actions.append(bids["k"][0])
+                actions['k'].append(bids["k"][0])
                 continue
-            # If all gets agree [how could they not?], just do it.
+            # If there are gets and they all agree [how could they not?], do it.
             if "g" in bids:
                 ga0 = bids["g"][0]
                 if util.all(a == ga0 for a in bids["g"][1:]):
                     repo.ui.note(" %s: picking 'get' action\n" % f)
-                    actions.append(ga0)
+                    actions['g'].append(ga0)
                     continue
             # TODO: Consider other simple actions such as mode changes
             # Handle inefficient democrazy.
             repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
-            for _f, m, args, msg in bidsl:
-                repo.ui.note('  %s -> %s\n' % (msg, m))
+            for m, l in sorted(bids.items()):
+                for _f, args, msg in l:
+                    repo.ui.note('  %s -> %s\n' % (msg, m))
             # Pick random action. TODO: Instead, prompt user when resolving
-            a0 = bidsl[0]
+            m, l = bids.items()[0]
             repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
-                         (f, a0[1]))
-            actions.append(a0)
+                         (f, m))
+            actions[m].append(l[0])
             continue
         repo.ui.note(_('end of auction\n\n'))
 
-    # Filter out prompts.
-    newactions, prompts = [], []
-    for a in actions:
-        if a[1] in ("cd", "dc"):
-            prompts.append(a)
-        else:
-            newactions.append(a)
     # Prompt and create actions. TODO: Move this towards resolve phase.
-    for f, m, args, msg in sorted(prompts):
-        if m == "cd":
+    if True:
+        for f, args, msg in actions['cd']:
             if repo.ui.promptchoice(
                 _("local changed %s which remote deleted\n"
                   "use (c)hanged version or (d)elete?"
                   "$$ &Changed $$ &Delete") % f, 0):
-                newactions.append((f, "r", None, "prompt delete"))
+                actions['r'].append((f, None, "prompt delete"))
             else:
-                newactions.append((f, "a", None, "prompt keep"))
-        elif m == "dc":
+                actions['a'].append((f, None, "prompt keep"))
+        del actions['cd'][:]
+
+        for f, args, msg in actions['dc']:
             flags, = args
             if repo.ui.promptchoice(
                 _("remote changed %s which local deleted\n"
                   "use (c)hanged version or leave (d)eleted?"
                   "$$ &Changed $$ &Deleted") % f, 0) == 0:
-                newactions.append((f, "g", (flags,), "prompt recreating"))
-        else: assert False, m
+                actions['g'].append((f, (flags,), "prompt recreating"))
+        del actions['dc'][:]
 
     if wctx.rev() is None:
-        newactions += _forgetremoved(wctx, mctx, branchmerge)
+        ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
+        actions['r'].extend(ractions)
+        actions['f'].extend(factions)
 
-    return newactions
+    return actions
 
 def recordupdates(repo, actions, branchmerge):
     "record merge actions to the dirstate"
-
-    for f, m, args, msg in actions:
-
+    if True:
         # remove (must come first)
-        if m == "r": # remove
+        for f, args, msg in actions['r']:
             if branchmerge:
                 repo.dirstate.remove(f)
             else:
                 repo.dirstate.drop(f)
 
         # forget (must come first)
-        elif m == "f":
+        for f, args, msg in actions['f']:
             repo.dirstate.drop(f)
 
         # re-add
-        elif m == "a":
+        for f, args, msg in actions['a']:
             if not branchmerge:
                 repo.dirstate.add(f)
 
         # exec change
-        elif m == "e":
+        for f, args, msg in actions['e']:
             repo.dirstate.normallookup(f)
 
         # keep
-        elif m == "k":
+        for f, args, msg in actions['k']:
             pass
 
         # get
-        elif m == "g":
+        for f, args, msg in actions['g']:
             if branchmerge:
                 repo.dirstate.otherparent(f)
             else:
                 repo.dirstate.normal(f)
 
         # merge
-        elif m == "m":
+        for f, args, msg in actions['m']:
             f1, f2, fa, move, anc = args
             if branchmerge:
                 # We've done a branch merge, mark this file as merged
@@ -968,7 +935,7 @@
                     repo.dirstate.drop(f1)
 
         # directory rename, move local
-        elif m == "dm":
+        for f, args, msg in actions['dm']:
             f0, flag = args
             if f0 not in repo.dirstate:
                 # untracked file moved
@@ -982,7 +949,7 @@
                 repo.dirstate.drop(f0)
 
         # directory rename, get
-        elif m == "dg":
+        for f, args, msg in actions['dg']:
             f0, flag = args
             if branchmerge:
                 repo.dirstate.add(f)