def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote, followcopies): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update partial = function to filter file lists acceptremote = accept the incoming changes without prompting """ 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 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev()) ] if followcopies: ret = copies.mergecopies(repo, wctx, p2, pa) copy, movewithdir, diverge, renamedelete = ret for of, fl in diverge.iteritems(): actions['dr'].append((of, (fl, ), "divergent renames")) for of, fl in renamedelete.iteritems(): actions['rd'].append((of, (fl, ), "rename and delete")) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" % (bool(branchmerge), bool(force), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) copied.update(movewithdir.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in sorted(wctx.substate): if wctx.sub(s).dirty(): m1['.hgsubstate'] += "+" break aborts = [] # Compare manifests diff = m1.diff(m2) for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): if partial and not partial(f): continue if n1 and n2: fa = f a = ma.get(f, nullid) if a == nullid: fa = copy.get(f, f) # Note: f as default is wrong - we can't really make a 3-way # merge without an ancestor file. fla = ma.flags(fa) nol = 'l' not in fl1 + fl2 + fla if n2 == a and fl2 == fla: 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['e'].append((f, (fl2, ), "update permissions")) else: actions['g'].append((f, (fl2, ), "remote is newer")) elif nol and n2 == a: # remote only changed 'x' actions['e'].append((f, (fl2, ), "update permissions")) elif nol and n1 == a: # local only changed 'x' actions['g'].append((f, (fl1, ), "remote is newer")) else: # both changed something 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['dm'].append( (f2, (f, fl1), "remote directory rename - move from " + f)) elif n1 and f in copy: f2 = copy[f] 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['r'].append((f, None, "remote delete")) else: actions['cd'].append((f, None, "prompt changed/deleted")) elif n1[20:] == "a": # added, no remote actions['f'].append((f, None, "remote deleted")) else: actions['r'].append((f, None, "other deleted")) elif n2 and f in movewithdir: f2 = movewithdir[f] 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['m'].append((f, (f2, f, f2, False, pa.node()), "remote copied from " + f2)) else: 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 # following table: # # force branchmerge different | action # n * n | get # n * y | abort # y n * | get # y y n | get # y y y | merge # # 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['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['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['g'].append((f, (fl2, ), "remote created")) elif n2 and n2 != ma[f]: different = _checkunknownfile(repo, wctx, p2, f) if not force and different: aborts.append((f, "ud")) else: # if different: old untracked f may be overwritten and lost if acceptremote: actions['g'].append( (f, (m2.flags(f), ), "remote recreating")) else: actions['dc'].append( (f, (m2.flags(f), ), "prompt deleted/changed")) for f, m in sorted(aborts): if m == "ud": repo.ui.warn(_("%s: untracked file differs\n") % f) else: assert False, m if aborts: raise util.Abort( _("untracked files in working directory differ " "from files in requested revision")) if not util.checkcase(repo.path): # 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, None) else: _checkcollision(repo, m1, actions) return actions
def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote=False): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update partial = function to filter file lists acceptremote = accept the incoming changes without prompting """ overwrite = force and not branchmerge actions, copy, movewithdir = [], {}, {} followcopies = False if overwrite: pa = wctx elif pa == p2: # backwards pa = wctx.p1() elif not branchmerge and not wctx.dirty(missing=True): pass elif pa and repo.ui.configbool("merge", "followcopies", True): followcopies = True # manifests fetched in order are going to be faster, so prime the caches [x.manifest() for x in sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())] if followcopies: 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")) for of, fl in renamedelete.iteritems(): actions.append((of, "rd", (fl,), "rename and delete")) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" % (bool(branchmerge), bool(force), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) copied.update(movewithdir.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in sorted(wctx.substate): if wctx.sub(s).dirty(): m1['.hgsubstate'] += "+" break aborts, prompts = [], [] # Compare manifests fdiff = dicthelpers.diff(m1, m2) flagsdiff = m1.flagsdiff(m2) diff12 = dicthelpers.join(fdiff, flagsdiff) for f, (n12, fl12) in diff12.iteritems(): if n12: n1, n2 = n12 else: # file contents didn't change, but flags did n1 = n2 = m1.get(f, None) if n1 is None: # Since n1 == n2, the file isn't present in m2 either. This # means that the file was removed or deleted locally and # removed remotely, but that residual entries remain in flags. # This can happen in manifests generated by workingctx. continue if fl12: fl1, fl2 = fl12 else: # flags didn't change, file contents did fl1 = fl2 = m1.flags(f) if partial and not partial(f): continue if n1 and n2: fla = ma.flags(f) nol = 'l' not in fl1 + fl2 + fla a = ma.get(f, nullid) if n2 == a and fl2 == fla: pass # remote unchanged - keep local elif n1 == a and fl1 == fla: # local unchanged - use remote if n1 == n2: # optimization: keep local content actions.append((f, "e", (fl2,), "update permissions")) else: actions.append((f, "g", (fl2,), "remote is newer")) elif nol and n2 == a: # remote only changed 'x' actions.append((f, "e", (fl2,), "update permissions")) elif nol and n1 == a: # local only changed 'x' actions.append((f, "g", (fl1,), "remote is newer")) else: # both changed something actions.append((f, "m", (f, f, False), "versions differ")) elif f in copied: # files we'll deal with on m2 side pass elif n1 and f in movewithdir: # directory rename f2 = movewithdir[f] actions.append((f, "d", (None, f2, fl1), "remote renamed directory to " + f2)) elif n1 and f in copy: f2 = copy[f] actions.append((f, "m", (f2, f, False), "local copied/moved to " + f2)) elif n1 and f in ma: # clean, a different, no remote if n1 != ma[f]: prompts.append((f, "cd")) # prompt changed/deleted elif n1[20:] == "a": # added, no remote actions.append((f, "f", None, "remote deleted")) else: actions.append((f, "r", None, "other deleted")) elif n2 and f in movewithdir: f2 = movewithdir[f] actions.append((None, "d", (f, f2, fl2), "local renamed directory to " + f2)) elif n2 and f in copy: f2 = copy[f] if f2 in m2: actions.append((f2, "m", (f, f, False), "remote copied to " + f)) else: actions.append((f2, "m", (f, f, True), "remote moved to " + f)) elif n2 and f not in ma: # local unknown, remote created: the logic is described by the # following table: # # force branchmerge different | action # n * n | get # n * y | abort # y n * | get # y y n | get # y y y | merge # # 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")) else: different = _checkunknownfile(repo, wctx, p2, f) if force and branchmerge and different: actions.append((f, "m", (f, f, False), "remote differs from untracked local")) elif not force and different: aborts.append((f, "ud")) else: actions.append((f, "g", (fl2,), "remote created")) elif n2 and n2 != ma[f]: prompts.append((f, "dc")) # prompt deleted/changed for f, m in sorted(aborts): if m == "ud": repo.ui.warn(_("%s: untracked file differs\n") % f) else: assert False, m if aborts: raise util.Abort(_("untracked files in working directory differ " "from files in requested revision")) if not util.checkcase(repo.path): # 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, [], []) else: _checkcollision(repo, m1, actions, prompts) for f, m in sorted(prompts): if m == "cd": if acceptremote: actions.append((f, "r", None, "remote delete")) elif repo.ui.promptchoice( _("local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?" "$$ &Changed $$ &Delete") % f, 0): actions.append((f, "r", None, "prompt delete")) else: actions.append((f, "a", None, "prompt keep")) elif m == "dc": if acceptremote: actions.append((f, "g", (m2.flags(f),), "remote recreating")) elif repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?" "$$ &Changed $$ &Deleted") % f, 0) == 0: actions.append((f, "g", (m2.flags(f),), "prompt recreating")) else: assert False, m return actions
def manifestmerge(repo, p1, p2, pa, overwrite, partial): """ Merge p1 and p2 with ancestor pa and generate merge action list overwrite = whether we clobber working files partial = function to filter file lists """ def fmerge(f, f2, fa): """merge flags""" a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2) if m == n: # flags agree return m # unchanged if m and n and not a: # flags set, don't agree, differ from parent r = repo.ui.promptchoice( _(" conflicting flags for %s\n" "(n)one, e(x)ec or sym(l)ink?") % f, (_("&None"), _("E&xec"), _("Sym&link")), 0) if r == 1: return "x" # Exec if r == 2: return "l" # Symlink return "" if m and m != a: # changed from a to m return m if n and n != a: # changed from a to n if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f): # can't automatically merge symlink flag when there # are file-level conflicts here, let filemerge take # care of it return m return n return '' # flag was cleared def act(msg, m, f, *args): repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) action.append((f, m) + args) action, copy = [], {} if overwrite: pa = p1 elif pa == p2: # backwards pa = p1.p1() elif pa and repo.ui.configbool("merge", "followcopies", True): dirs = repo.ui.configbool("merge", "followdirs", True) copy, diverge = copies.mergecopies(repo, p1, p2, pa, dirs) for of, fl in diverge.iteritems(): act("divergent renames", "dr", of, fl) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" overwrite: %s, partial: %s\n" % (bool(overwrite), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2)) m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in p1.substate: if p1.sub(s).dirty(): m1['.hgsubstate'] += "+" break # Compare manifests for f, n in m1.iteritems(): if partial and not partial(f): continue if f in m2: rflags = fmerge(f, f, f) a = ma.get(f, nullid) if n == m2[f] or m2[f] == a: # same or local newer # is file locally modified or flags need changing? # dirstate flags may need to be made current if m1.flags(f) != rflags or n[20:]: act("update permissions", "e", f, rflags) elif n == a: # remote newer act("remote is newer", "g", f, rflags) else: # both changed act("versions differ", "m", f, f, f, rflags, False) elif f in copied: # files we'll deal with on m2 side pass elif f in copy: f2 = copy[f] if f2 not in m2: # directory rename act("remote renamed directory to " + f2, "d", f, None, f2, m1.flags(f)) else: # case 2 A,B/B/B or case 4,21 A/B/B act("local copied/moved to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) elif f in ma: # clean, a different, no remote if n != ma[f]: if repo.ui.promptchoice( _(" local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?") % f, (_("&Changed"), _("&Delete")), 0): act("prompt delete", "r", f) else: act("prompt keep", "a", f) elif n[20:] == "a": # added, no remote act("remote deleted", "f", f) elif n[20:] != "u": act("other deleted", "r", f) for f, n in m2.iteritems(): if partial and not partial(f): continue if f in m1 or f in copied: # files already visited continue if f in copy: f2 = copy[f] if f2 not in m1: # directory rename act("local renamed directory to " + f2, "d", None, f, f2, m2.flags(f)) elif f2 in m2: # rename case 1, A/A,B/A act("remote copied to " + f, "m", f2, f, f, fmerge(f2, f, f2), False) else: # case 3,20 A/B/A act("remote moved to " + f, "m", f2, f, f, fmerge(f2, f, f2), True) elif f not in ma: act("remote created", "g", f, m2.flags(f)) elif n != ma[f]: if repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?") % f, (_("&Changed"), _("&Deleted")), 0) == 0: act("prompt recreating", "g", f, m2.flags(f)) return action
def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote, followcopies): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update partial = function to filter file lists acceptremote = accept the incoming changes without prompting """ copy, movewithdir, diverge, renamedelete = {}, {}, {}, {} # manifests fetched in order are going to be faster, so prime the caches [x.manifest() for x in sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())] if followcopies: ret = copies.mergecopies(repo, wctx, p2, pa) copy, movewithdir, diverge, renamedelete = ret repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" % (bool(branchmerge), bool(force), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) copied.update(movewithdir.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in sorted(wctx.substate): if wctx.sub(s).dirty(): m1['.hgsubstate'] += '+' break # Compare manifests diff = m1.diff(m2) actions = {} for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): if partial and not partial(f): continue if n1 and n2: # file exists on both local and remote side if f not in ma: fa = copy.get(f, None) if fa is not None: actions[f] = ('m', (f, f, fa, False, pa.node()), "both renamed from " + fa) else: actions[f] = ('m', (f, f, None, False, pa.node()), "both created") else: a = ma[f] fla = ma.flags(f) nol = 'l' not in fl1 + fl2 + fla if n2 == a and fl2 == fla: actions[f] = ('k' , (), "remote unchanged") elif n1 == a and fl1 == fla: # local unchanged - use remote if n1 == n2: # optimization: keep local content actions[f] = ('e', (fl2,), "update permissions") else: actions[f] = ('g', (fl2,), "remote is newer") elif nol and n2 == a: # remote only changed 'x' actions[f] = ('e', (fl2,), "update permissions") elif nol and n1 == a: # local only changed 'x' actions[f] = ('g', (fl1,), "remote is newer") else: # both changed something actions[f] = ('m', (f, f, f, False, pa.node()), "versions differ") elif n1: # file exists only on local side if f in copied: pass # we'll deal with it on m2 side elif f in movewithdir: # directory rename, move local f2 = movewithdir[f] if f2 in m2: actions[f2] = ('m', (f, f2, None, True, pa.node()), "remote directory rename, both created") else: actions[f2] = ('dm', (f, fl1), "remote directory rename - move from " + f) elif f in copy: f2 = copy[f] actions[f] = ('m', (f, f2, f2, False, pa.node()), "local copied/moved from " + f2) elif f in ma: # clean, a different, no remote if n1 != ma[f]: if acceptremote: actions[f] = ('r', None, "remote delete") else: actions[f] = ('cd', None, "prompt changed/deleted") elif n1[20:] == 'a': # This extra 'a' is added by working copy manifest to mark # the file as locally added. We should forget it instead of # deleting it. actions[f] = ('f', None, "remote deleted") else: actions[f] = ('r', None, "other deleted") elif n2: # file exists only on remote side if f in copied: pass # we'll deal with it on m1 side elif f in movewithdir: f2 = movewithdir[f] if f2 in m1: actions[f2] = ('m', (f2, f, None, False, pa.node()), "local directory rename, both created") else: actions[f2] = ('dg', (f, fl2), "local directory rename - get from " + f) elif f in copy: f2 = copy[f] if f2 in m2: actions[f] = ('m', (f2, f, f2, False, pa.node()), "remote copied from " + f2) else: actions[f] = ('m', (f2, f, f2, True, pa.node()), "remote moved from " + f2) elif f not in ma: # local unknown, remote created: the logic is described by the # following table: # # force branchmerge different | action # n * * | create # y n * | create # y y n | create # y y y | merge # # Checking whether the files are different is expensive, so we # don't do that when we can avoid it. if not force: actions[f] = ('c', (fl2,), "remote created") elif not branchmerge: actions[f] = ('c', (fl2,), "remote created") else: actions[f] = ('cm', (fl2, pa.node()), "remote created, get or merge") elif n2 != ma[f]: if acceptremote: actions[f] = ('c', (fl2,), "remote recreating") else: actions[f] = ('dc', (fl2,), "prompt deleted/changed") return actions, diverge, renamedelete
def manifestmerge(repo, p1, p2, pa, overwrite, partial): """ Merge p1 and p2 with ancestor pa and generate merge action list overwrite = whether we clobber working files partial = function to filter file lists """ def fmerge(f, f2, fa): """merge flags""" a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2) if m == n: # flags agree return m # unchanged if m and n and not a: # flags set, don't agree, differ from parent r = repo.ui.promptchoice( _(" conflicting flags for %s\n" "(n)one, e(x)ec or sym(l)ink?") % f, (_("&None"), _("E&xec"), _("Sym&link")), 0, ) if r == 1: return "x" # Exec if r == 2: return "l" # Symlink return "" if m and m != a: # changed from a to m return m if n and n != a: # changed from a to n if (n == "l" or a == "l") and m1.get(f) != ma.get(f): # can't automatically merge symlink flag when there # are file-level conflicts here, let filemerge take # care of it return m return n return "" # flag was cleared def act(msg, m, f, *args): repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) action.append((f, m) + args) action, copy = [], {} if overwrite: pa = p1 elif pa == p2: # backwards pa = p1.p1() elif pa and repo.ui.configbool("merge", "followcopies", True): copy, diverge = copies.mergecopies(repo, p1, p2, pa) for of, fl in diverge.iteritems(): act("divergent renames", "dr", of, fl) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" overwrite: %s, partial: %s\n" % (bool(overwrite), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2)) m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) if ".hgsubstate" in m1: # check whether sub state is modified for s in p1.substate: if p1.sub(s).dirty(): m1[".hgsubstate"] += "+" break # Compare manifests for f, n in m1.iteritems(): if partial and not partial(f): continue if f in m2: rflags = fmerge(f, f, f) a = ma.get(f, nullid) if n == m2[f] or m2[f] == a: # same or local newer # is file locally modified or flags need changing? # dirstate flags may need to be made current if m1.flags(f) != rflags or n[20:]: act("update permissions", "e", f, rflags) elif n == a: # remote newer act("remote is newer", "g", f, rflags) else: # both changed act("versions differ", "m", f, f, f, rflags, False) elif f in copied: # files we'll deal with on m2 side pass elif f in copy: f2 = copy[f] if f2 not in m2: # directory rename act("remote renamed directory to " + f2, "d", f, None, f2, m1.flags(f)) else: # case 2 A,B/B/B or case 4,21 A/B/B act("local copied/moved to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) elif f in ma: # clean, a different, no remote if n != ma[f]: if repo.ui.promptchoice( _(" local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?") % f, (_("&Changed"), _("&Delete")), 0, ): act("prompt delete", "r", f) else: act("prompt keep", "a", f) elif n[20:] == "a": # added, no remote act("remote deleted", "f", f) else: act("other deleted", "r", f) for f, n in m2.iteritems(): if partial and not partial(f): continue if f in m1 or f in copied: # files already visited continue if f in copy: f2 = copy[f] if f2 not in m1: # directory rename act("local renamed directory to " + f2, "d", None, f, f2, m2.flags(f)) elif f2 in m2: # rename case 1, A/A,B/A act("remote copied to " + f, "m", f2, f, f, fmerge(f2, f, f2), False) else: # case 3,20 A/B/A act("remote moved to " + f, "m", f2, f, f, fmerge(f2, f, f2), True) elif f not in ma: if not overwrite and _checkunknownfile(repo, p1, p2, f): rflags = fmerge(f, f, f) act("remote differs from untracked local", "m", f, f, f, rflags, False) else: act("remote created", "g", f, m2.flags(f)) elif n != ma[f]: if ( repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?") % f, (_("&Changed"), _("&Deleted")), 0, ) == 0 ): act("prompt recreating", "g", f, m2.flags(f)) return action
def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote, followcopies): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update partial = function to filter file lists acceptremote = accept the incoming changes without prompting """ 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 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())] if followcopies: ret = copies.mergecopies(repo, wctx, p2, pa) copy, movewithdir, diverge, renamedelete = ret for of, fl in diverge.iteritems(): actions['dr'].append((of, (fl,), "divergent renames")) for of, fl in renamedelete.iteritems(): actions['rd'].append((of, (fl,), "rename and delete")) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" % (bool(branchmerge), bool(force), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) copied.update(movewithdir.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in sorted(wctx.substate): if wctx.sub(s).dirty(): m1['.hgsubstate'] += "+" break aborts = [] # Compare manifests diff = m1.diff(m2) for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): if partial and not partial(f): continue if n1 and n2: fa = f a = ma.get(f, nullid) if a == nullid: fa = copy.get(f, f) # Note: f as default is wrong - we can't really make a 3-way # merge without an ancestor file. fla = ma.flags(fa) nol = 'l' not in fl1 + fl2 + fla if n2 == a and fl2 == fla: 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['e'].append((f, (fl2,), "update permissions")) else: actions['g'].append((f, (fl2,), "remote is newer")) elif nol and n2 == a: # remote only changed 'x' actions['e'].append((f, (fl2,), "update permissions")) elif nol and n1 == a: # local only changed 'x' actions['g'].append((f, (fl1,), "remote is newer")) else: # both changed something 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['dm'].append((f2, (f, fl1), "remote directory rename - move from " + f)) elif n1 and f in copy: f2 = copy[f] 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['r'].append((f, None, "remote delete")) else: actions['cd'].append((f, None, "prompt changed/deleted")) elif n1[20:] == "a": # added, no remote actions['f'].append((f, None, "remote deleted")) else: actions['r'].append((f, None, "other deleted")) elif n2 and f in movewithdir: f2 = movewithdir[f] 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['m'].append((f, (f2, f, f2, False, pa.node()), "remote copied from " + f2)) else: 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 # following table: # # force branchmerge different | action # n * n | get # n * y | abort # y n * | get # y y n | get # y y y | merge # # 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['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['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['g'].append((f, (fl2,), "remote created")) elif n2 and n2 != ma[f]: different = _checkunknownfile(repo, wctx, p2, f) if not force and different: aborts.append((f, "ud")) else: # if different: old untracked f may be overwritten and lost if acceptremote: actions['g'].append((f, (m2.flags(f),), "remote recreating")) else: actions['dc'].append((f, (m2.flags(f),), "prompt deleted/changed")) for f, m in sorted(aborts): if m == "ud": repo.ui.warn(_("%s: untracked file differs\n") % f) else: assert False, m if aborts: raise util.Abort(_("untracked files in working directory differ " "from files in requested revision")) if not util.checkcase(repo.path): # 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, None) else: _checkcollision(repo, m1, actions) return actions
def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote=False): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update partial = function to filter file lists acceptremote = accept the incoming changes without prompting """ overwrite = force and not branchmerge actions, copy, movewithdir = [], {}, {} followcopies = False if overwrite: pa = wctx elif pa == p2: # backwards pa = wctx.p1() elif not branchmerge and not wctx.dirty(missing=True): pass elif pa and repo.ui.configbool("merge", "followcopies", True): followcopies = True # manifests fetched in order are going to be faster, so prime the caches [ x.manifest() for x in sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev()) ] if followcopies: 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")) for of, fl in renamedelete.iteritems(): actions.append((of, "rd", (fl, ), "rename and delete")) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" % (bool(branchmerge), bool(force), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) copied.update(movewithdir.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in sorted(wctx.substate): if wctx.sub(s).dirty(): m1['.hgsubstate'] += "+" break aborts, prompts = [], [] # Compare manifests fdiff = dicthelpers.diff(m1, m2) flagsdiff = m1.flagsdiff(m2) diff12 = dicthelpers.join(fdiff, flagsdiff) for f, (n12, fl12) in diff12.iteritems(): if n12: n1, n2 = n12 else: # file contents didn't change, but flags did n1 = n2 = m1.get(f, None) if n1 is None: # Since n1 == n2, the file isn't present in m2 either. This # means that the file was removed or deleted locally and # removed remotely, but that residual entries remain in flags. # This can happen in manifests generated by workingctx. continue if fl12: fl1, fl2 = fl12 else: # flags didn't change, file contents did fl1 = fl2 = m1.flags(f) if partial and not partial(f): continue if n1 and n2: fla = ma.flags(f) nol = 'l' not in fl1 + fl2 + fla a = ma.get(f, nullid) if n2 == a and fl2 == fla: pass # remote unchanged - keep local elif n1 == a and fl1 == fla: # local unchanged - use remote if n1 == n2: # optimization: keep local content actions.append((f, "e", (fl2, ), "update permissions")) else: actions.append((f, "g", (fl2, ), "remote is newer")) elif nol and n2 == a: # remote only changed 'x' actions.append((f, "e", (fl2, ), "update permissions")) elif nol and n1 == a: # local only changed 'x' actions.append((f, "g", (fl1, ), "remote is newer")) else: # both changed something actions.append((f, "m", (f, f, False), "versions differ")) elif f in copied: # files we'll deal with on m2 side pass elif n1 and f in movewithdir: # directory rename f2 = movewithdir[f] actions.append( (f, "d", (None, f2, fl1), "remote renamed directory to " + f2)) elif n1 and f in copy: f2 = copy[f] actions.append( (f, "m", (f2, f, False), "local copied/moved to " + f2)) elif n1 and f in ma: # clean, a different, no remote if n1 != ma[f]: prompts.append((f, "cd")) # prompt changed/deleted elif n1[20:] == "a": # added, no remote actions.append((f, "f", None, "remote deleted")) else: actions.append((f, "r", None, "other deleted")) elif n2 and f in movewithdir: f2 = movewithdir[f] actions.append( (None, "d", (f, f2, fl2), "local renamed directory to " + f2)) elif n2 and f in copy: f2 = copy[f] if f2 in m2: actions.append( (f2, "m", (f, f, False), "remote copied to " + f)) else: actions.append((f2, "m", (f, f, True), "remote moved to " + f)) elif n2 and f not in ma: # local unknown, remote created: the logic is described by the # following table: # # force branchmerge different | action # n * n | get # n * y | abort # y n * | get # y y n | get # y y y | merge # # 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")) else: different = _checkunknownfile(repo, wctx, p2, f) if force and branchmerge and different: actions.append((f, "m", (f, f, False), "remote differs from untracked local")) elif not force and different: aborts.append((f, "ud")) else: actions.append((f, "g", (fl2, ), "remote created")) elif n2 and n2 != ma[f]: prompts.append((f, "dc")) # prompt deleted/changed for f, m in sorted(aborts): if m == "ud": repo.ui.warn(_("%s: untracked file differs\n") % f) else: assert False, m if aborts: raise util.Abort( _("untracked files in working directory differ " "from files in requested revision")) if not util.checkcase(repo.path): # 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, [], []) else: _checkcollision(repo, m1, actions, prompts) for f, m in sorted(prompts): if m == "cd": if acceptremote: actions.append((f, "r", None, "remote delete")) elif repo.ui.promptchoice( _("local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?" "$$ &Changed $$ &Delete") % f, 0): actions.append((f, "r", None, "prompt delete")) else: actions.append((f, "a", None, "prompt keep")) elif m == "dc": if acceptremote: actions.append((f, "g", (m2.flags(f), ), "remote recreating")) elif repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?" "$$ &Changed $$ &Deleted") % f, 0) == 0: actions.append((f, "g", (m2.flags(f), ), "prompt recreating")) else: assert False, m return actions
def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, acceptremote, followcopies): """ Merge p1 and p2 with ancestor pa and generate merge action list branchmerge and force are as passed in to update partial = function to filter file lists acceptremote = accept the incoming changes without prompting """ copy, movewithdir, diverge, renamedelete = {}, {}, {}, {} # manifests fetched in order are going to be faster, so prime the caches [ x.manifest() for x in sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev()) ] if followcopies: ret = copies.mergecopies(repo, wctx, p2, pa) copy, movewithdir, diverge, renamedelete = ret repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n" % (bool(branchmerge), bool(force), bool(partial))) repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2)) m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) copied.update(movewithdir.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in sorted(wctx.substate): if wctx.sub(s).dirty(): m1['.hgsubstate'] += '+' break # Compare manifests diff = m1.diff(m2) actions = {} for f, ((n1, fl1), (n2, fl2)) in diff.iteritems(): if partial and not partial(f): continue if n1 and n2: # file exists on both local and remote side if f not in ma: fa = copy.get(f, None) if fa is not None: actions[f] = ('m', (f, f, fa, False, pa.node()), "both renamed from " + fa) else: actions[f] = ('m', (f, f, None, False, pa.node()), "both created") else: a = ma[f] fla = ma.flags(f) nol = 'l' not in fl1 + fl2 + fla if n2 == a and fl2 == fla: actions[f] = ('k', (), "remote unchanged") elif n1 == a and fl1 == fla: # local unchanged - use remote if n1 == n2: # optimization: keep local content actions[f] = ('e', (fl2, ), "update permissions") else: actions[f] = ('g', (fl2, ), "remote is newer") elif nol and n2 == a: # remote only changed 'x' actions[f] = ('e', (fl2, ), "update permissions") elif nol and n1 == a: # local only changed 'x' actions[f] = ('g', (fl1, ), "remote is newer") else: # both changed something actions[f] = ('m', (f, f, f, False, pa.node()), "versions differ") elif n1: # file exists only on local side if f in copied: pass # we'll deal with it on m2 side elif f in movewithdir: # directory rename, move local f2 = movewithdir[f] if f2 in m2: actions[f2] = ('m', (f, f2, None, True, pa.node()), "remote directory rename, both created") else: actions[f2] = ('dm', (f, fl1), "remote directory rename - move from " + f) elif f in copy: f2 = copy[f] actions[f] = ('m', (f, f2, f2, False, pa.node()), "local copied/moved from " + f2) elif f in ma: # clean, a different, no remote if n1 != ma[f]: if acceptremote: actions[f] = ('r', None, "remote delete") else: actions[f] = ('cd', None, "prompt changed/deleted") elif n1[20:] == 'a': # This extra 'a' is added by working copy manifest to mark # the file as locally added. We should forget it instead of # deleting it. actions[f] = ('f', None, "remote deleted") else: actions[f] = ('r', None, "other deleted") elif n2: # file exists only on remote side if f in copied: pass # we'll deal with it on m1 side elif f in movewithdir: f2 = movewithdir[f] if f2 in m1: actions[f2] = ('m', (f2, f, None, False, pa.node()), "local directory rename, both created") else: actions[f2] = ('dg', (f, fl2), "local directory rename - get from " + f) elif f in copy: f2 = copy[f] if f2 in m2: actions[f] = ('m', (f2, f, f2, False, pa.node()), "remote copied from " + f2) else: actions[f] = ('m', (f2, f, f2, True, pa.node()), "remote moved from " + f2) elif f not in ma: # local unknown, remote created: the logic is described by the # following table: # # force branchmerge different | action # n * * | create # y n * | create # y y n | create # y y y | merge # # Checking whether the files are different is expensive, so we # don't do that when we can avoid it. if not force: actions[f] = ('c', (fl2, ), "remote created") elif not branchmerge: actions[f] = ('c', (fl2, ), "remote created") else: actions[f] = ('cm', (fl2, pa.node()), "remote created, get or merge") elif n2 != ma[f]: if acceptremote: actions[f] = ('c', (fl2, ), "remote recreating") else: actions[f] = ('dc', (fl2, ), "prompt deleted/changed") return actions, diverge, renamedelete