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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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
Exemple #5
0
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
Exemple #7
0
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
Exemple #8
0
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