Beispiel #1
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
Beispiel #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