diff -r 6062593d8b06 -r 43eecb4e23f8 mercurial/merge.py --- 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)