示例#1
0
                def _rewritesingle(c, _commitopts):
                    # Predefined message overrides other message editing choices.
                    msg = msgmap.get(c.node())
                    if msg is not None:
                        _commitopts["message"] = msg
                        _commitopts["edit"] = False
                    if _commitopts.get("edit", False):
                        _commitopts["message"] = (
                            "HG: Commit message of changeset %s\n%s" %
                            (str(c), c.description()))
                    bases = [
                        replacemap.get(c.p1().node(),
                                       c.p1().node()),
                        replacemap.get(c.p2().node(),
                                       c.p2().node()),
                    ]
                    if mutation.enabled(repo):
                        preds = [
                            replacemap[p] for p in mutation.predecessorsset(
                                repo, c.node(), closest=True)
                            if p in replacemap
                        ]
                    else:
                        preds = []

                    newid, created = common.metarewrite(repo,
                                                        c,
                                                        bases,
                                                        commitopts=_commitopts,
                                                        copypreds=preds)
                    if created:
                        replacemap[c.node()] = newid
示例#2
0
文件: smartlog.py 项目: jsoref/eden
def histeditsuccessors(repo, ctx, **args):
    """Return all of the node's successors created as a result of
       histedit
    """
    if mutation.enabled(repo):
        return ""
    asnodes = list(modifysuccessors(ctx, "histedit"))
    return templatekw.showlist("histeditsuccessor", asnodes, args)
示例#3
0
文件: revsets.py 项目: xmonader/eden
def _destrestack(repo, subset, x):
    """restack destination for given single source revision"""
    unfi = repo.unfiltered()
    obsoleted = unfi.revs("obsolete()")
    getparents = unfi.changelog.parentrevs
    getphase = unfi._phasecache.phase
    nodemap = unfi.changelog.nodemap

    src = revset.getset(repo, subset, x).first()

    # Empty src or already obsoleted - Do not return a destination
    if not src or src in obsoleted:
        return smartset.baseset()

    # Find the obsoleted "base" by checking source's parent recursively
    base = src
    while base not in obsoleted:
        base = getparents(base)[0]
        # When encountering a public revision which cannot be obsoleted, stop
        # the search early and return no destination. Do the same for nullrev.
        if getphase(repo, base) == phases.public or base == nullrev:
            return smartset.baseset()

    # Find successors for given base
    # NOTE: Ideally we can use obsutil.successorssets to detect divergence
    # case. However it does not support cycles (unamend) well. So we use
    # allsuccessors and pick non-obsoleted successors manually as a workaround.
    basenode = repo[base].node()
    if mutation.enabled(repo):
        succnodes = mutation.allsuccessors(repo, [basenode])
    else:
        succnodes = obsutil.allsuccessors(repo.obsstore, [basenode])
    succnodes = [
        n for n in succnodes
        if (n != basenode and n in nodemap and nodemap[n] not in obsoleted)
    ]

    # In case of a split, only keep its heads
    succrevs = list(unfi.revs("heads(%ln)", succnodes))

    if len(succrevs) == 0:
        # Prune - Find the first non-obsoleted ancestor
        while base in obsoleted:
            base = getparents(base)[0]
            if base == nullrev:
                # Root node is pruned. The new base (destination) is the
                # virtual nullrev.
                return smartset.baseset([nullrev])
        return smartset.baseset([base])
    elif len(succrevs) == 1:
        # Unique visible successor case - A valid destination
        return smartset.baseset([succrevs[0]])
    else:
        # Multiple visible successors - Choose the one with a greater revision
        # number. This is to be compatible with restack old behavior. We might
        # want to revisit it when we introduce the divergence concept to users.
        return smartset.baseset([max(succrevs)])
示例#4
0
def smarthide(repo, revhide, revshow, local=False):
    """hides changecontexts and reveals some commits

    tries to connect related hides and shows with obs marker
    when reasonable and correct

    use local to not hide revhides without corresponding revshows
    """
    hidectxs = repo.set(revhide)
    showctxs = repo.set(revshow)
    markers = []
    nodes = []
    for ctx in hidectxs:
        unfi = repo.unfiltered()
        related = set()
        if mutation.enabled(unfi):
            related.update(mutation.allpredecessors(unfi, [ctx.node()]))
            related.update(mutation.allsuccessors(unfi, [ctx.node()]))
        else:
            related.update(obsutil.allpredecessors(unfi.obsstore,
                                                   [ctx.node()]))
            related.update(obsutil.allsuccessors(unfi.obsstore, [ctx.node()]))
        related.intersection_update(x.node() for x in showctxs)
        destinations = [repo[x] for x in related]

        # two primary objectives:
        # 1. correct divergence/nondivergence
        # 2. correct visibility of changesets for the user
        # secondary objectives:
        # 3. useful ui message in hg sl: "Undone to"
        # Design choices:
        # 1-to-1 correspondence is easy
        # 1-to-many correspondence is hard:
        #   it's either divergent A to B, A to C
        #   or split A to B,C
        #   because of undo we don't know which
        #   without complex logic
        # Solution: provide helpful ui message for
        # common and easy case (1 to 1), use simplest
        # correct solution for complex edge case

        if len(destinations) == 1:
            markers.append((ctx, destinations))
            nodes.append(ctx.node())
        elif len(destinations) > 1:  # split
            markers.append((ctx, []))
            nodes.append(ctx.node())
        elif len(destinations) == 0:
            if not local:
                markers.append((ctx, []))
                nodes.append(ctx.node())

    if obsolete.isenabled(repo, obsolete.createmarkersopt):
        obsolete.createmarkers(repo, markers, operation="undo")
    visibility.remove(repo, nodes)
示例#5
0
文件: smartlog.py 项目: jsoref/eden
def singlepublicsuccessor(repo, ctx, templ, **args):
    """String. Get a single public successor for a
    given node.  If there's none or more than one, return empty string.
    This is intended to be used for "Landed as" marking
    in `hg sl` output."""
    if mutation.enabled(repo):
        return ""
    successorssets = obsutil.successorssets(repo, ctx.node())
    unfiltered = repo.unfiltered()
    ctxs = (unfiltered[n]
            for n in itertools.chain.from_iterable(successorssets))
    public = (c.hex() for c in ctxs if not c.mutable() and c != ctx)
    first = next(public, "")
    second = next(public, "")

    return "" if first and second else first
示例#6
0
def unamend(ui, repo, **opts):
    """undo the last amend operation on the current commit

    Reverse the effects of an :hg:`amend` operation. Hides the current commit
    and checks out the previous version of the commit. :hg:`unamend` does not
    revert the state of the working copy, so changes that were added to the
    commit in the last amend operation become pending changes in the working
    copy.

    :hg:`unamend` cannot be run on amended commits that have children. In
    other words, you cannot unamend an amended commit in the middle of a
    stack.

    .. note::

        Running :hg:`unamend` is similar to running :hg:`undo --keep`
        immediately after :hg:`amend`. However, unlike :hg:`undo`, which can
        only undo an amend if it was the last operation you performed,
        :hg:`unamend` can unamend any draft amended commit in the graph that
        does not have children.

    .. container:: verbose

      Although :hg:`unamend` is typically used to reverse the effects of
      :hg:`amend`, it actually rolls back the current commit to its previous
      version, regardless of whether the changes resulted from an :hg:`amend`
      operation or from another operation, such as :hg:`rebase`.
    """
    unfi = repo

    # identify the commit from which to unamend
    curctx = repo["."]

    # identify the commit to which to unamend
    if mutation.enabled(repo):
        prednodes = curctx.mutationpredecessors()
        if not prednodes:
            prednodes = []
    else:
        prednodes = [marker.prednode() for marker in predecessormarkers(curctx)]

    if len(prednodes) != 1:
        e = _("changeset must have one predecessor, found %i predecessors")
        raise error.Abort(e % len(prednodes))
    prednode = prednodes[0]

    if prednode not in unfi:
        # Trigger autopull.
        autopull.trypull(unfi, [nodemod.hex(prednode)])

    predctx = unfi[prednode]

    if curctx.children():
        raise error.Abort(_("cannot unamend in the middle of a stack"))

    with repo.wlock(), repo.lock():
        ctxbookmarks = curctx.bookmarks()
        changedfiles = []
        wctx = repo[None]
        wm = wctx.manifest()
        cm = predctx.manifest()
        dirstate = repo.dirstate
        diff = cm.diff(wm)
        changedfiles.extend(pycompat.iterkeys(diff))

        tr = repo.transaction("unamend")
        with dirstate.parentchange():
            dirstate.rebuild(prednode, cm, changedfiles)
            # we want added and removed files to be shown
            # properly, not with ? and ! prefixes
            for filename, data in pycompat.iteritems(diff):
                if data[0][0] is None:
                    dirstate.add(filename)
                if data[1][0] is None:
                    dirstate.remove(filename)
        changes = []
        for book in ctxbookmarks:
            changes.append((book, prednode))
        repo._bookmarks.applychanges(repo, tr, changes)
        if obsolete.isenabled(repo, obsolete.createmarkersopt):
            obsolete.createmarkers(repo, [(curctx, (predctx,))])
        visibility.remove(repo, [curctx.node()])
        visibility.add(repo, [predctx.node()])
        tr.close()
示例#7
0
def _getscratchbranchpartsimpl(
    repo, peer, outgoing, confignonforwardmove, ui, bookmark, create, bookmarknode=None
):
    _validaterevset(repo, revsetlang.formatspec("%ln", outgoing.missing), bookmark)

    supportedversions = changegroup.supportedoutgoingversions(repo)
    # Explicitly avoid using '01' changegroup version in infinitepush to
    # support general delta
    supportedversions.discard("01")
    cgversion = min(supportedversions)
    _handlelfs(repo, outgoing.missing)
    cg = changegroup.makestream(repo, outgoing, cgversion, "push")

    params = {}
    params["cgversion"] = cgversion
    if bookmark:
        params["bookmark"] = bookmark
        if bookmarknode:
            params["bookmarknode"] = bookmarknode
        if create:
            params["create"] = "1"
    if confignonforwardmove:
        params["force"] = "1"

    parts = []

    # .upper() marks this as a mandatory part: server will abort if there's no
    #  handler
    parts.append(
        bundle2.bundlepart(
            constants.scratchbranchparttype.upper(),
            advisoryparams=pycompat.iteritems(params),
            data=cg,
        )
    )

    if mutation.enabled(repo):
        entries = mutation.entriesforbundle(repo, outgoing.missing)
        if entries:
            if constants.scratchmutationparttype not in bundle2.bundle2caps(peer):
                repo.ui.warn(
                    _("no server support for %r - skipping\n")
                    % constants.scratchmutationparttype
                )
            else:
                parts.append(
                    bundle2.bundlepart(
                        constants.scratchmutationparttype,
                        data=mutation.bundleentries(entries),
                    )
                )

    try:
        treemod = extensions.find("treemanifest")
        remotefilelog = extensions.find("remotefilelog")
        sendtrees = remotefilelog.shallowbundle.cansendtrees(repo, outgoing.missing)
        if sendtrees != remotefilelog.shallowbundle.NoTrees:
            parts.append(
                treemod.createtreepackpart(
                    repo, outgoing, treemod.TREEGROUP_PARTTYPE2, sendtrees=sendtrees
                )
            )
    except KeyError:
        pass

    try:
        snapshot = extensions.find("snapshot")
    except KeyError:
        pass
    else:
        snapshot.bundleparts.appendsnapshotmetadatabundlepart(
            repo, outgoing.missing, parts
        )

    return parts
示例#8
0
文件: metaedit.py 项目: simpkins/eden
def metaedit(ui, repo, templ, *revs, **opts):
    """edit commit message and other metadata

    Edit commit message for the current commit. By default, opens your default
    editor so that you can edit the commit message interactively. Specify -m
    to specify the commit message on the command line.

    To edit the message for a different commit, specify -r. To edit the
    messages of multiple commits, specify --batch.

    You can edit other pieces of commit metadata, namely the user or date,
    by specifying -u or -d, respectively. The expected format for user is
    'Full Name <*****@*****.**>'.

    There is also automation-friendly JSON input mode which allows the caller
    to provide the mapping between commit and new message and username in the
    following format:

        {
            "<commit_hash>": {
                "message": "<message>",
                "user": "******" // optional
            }
        }

    .. note::

        You can specify --fold to fold multiple revisions into one when the
        given revisions form a linear unbroken chain. However, :hg:`fold` is
        the preferred command for this purpose. See :hg:`help fold` for more
        information.

    .. container:: verbose

     Some examples:

     - Edit the commit message for the current commit::

         hg metaedit

     - Change the username for the current commit::

         hg metaedit --user 'New User <*****@*****.**>'

    """
    revs = list(revs)
    revs.extend(opts["rev"])
    if not revs:
        if opts["fold"]:
            raise error.Abort(_("revisions must be specified with --fold"))
        revs = ["."]

    with repo.wlock(), repo.lock():
        revs = scmutil.revrange(repo, revs)
        msgmap = {
        }  # {node: message}, predefined messages, currently used by --batch
        usermap = {
        }  # {node: author}, predefined authors, used by --jsoninputfile

        if opts["fold"]:
            root, head = fold._foldcheck(repo, revs)
        else:
            if repo.revs("%ld and public()", revs):
                raise error.Abort(
                    _("cannot edit commit information for public "
                      "revisions"))
            root = head = repo[revs.first()]

        wctx = repo[None]
        p1 = wctx.p1()
        tr = repo.transaction("metaedit")
        newp1 = None
        try:
            commitopts = opts.copy()
            allctx = [repo[r] for r in revs]
            jsoninputfile = None

            if any(
                    commitopts.get(name)
                    for name in ["message", "logfile", "reuse_message"]):
                commitopts["edit"] = False
            else:
                if opts["fold"]:
                    msgs = [
                        _("HG: This is a fold of %d changesets.") % len(allctx)
                    ]
                    msgs += [
                        _("HG: Commit message of %s.\n\n%s\n") %
                        (nodemod.short(c.node()), c.description())
                        for c in allctx
                    ]
                else:
                    if opts["batch"] and len(revs) > 1:
                        msgmap = editmessages(repo, revs)

                    msgs = [head.description()]
                    jsoninputfile = opts.get("json_input_file")
                    if jsoninputfile:
                        try:
                            if cmdutil.isstdiofilename(jsoninputfile):
                                inputjson = pycompat.decodeutf8(ui.fin.read())
                            else:
                                inputjson = pycompat.decodeutf8(
                                    util.readfile(jsoninputfile))
                            msgusermap = json.loads(inputjson)
                        except IOError as inst:
                            raise error.Abort(
                                _("can't read JSON input file '%s': %s") %
                                (jsoninputfile,
                                 encoding.strtolocal(inst.strerror)))
                        except ValueError as inst:
                            raise error.Abort(
                                _("can't decode JSON input file '%s': %s") %
                                (jsoninputfile, str(inst)))

                        if not isinstance(msgusermap, dict):
                            raise error.Abort(
                                _("JSON input is not a dictionary (see --help for input format)"
                                  ))

                        try:
                            msgmap = {
                                bin(node): msguser.get("message")
                                for (node, msguser) in msgusermap.items()
                                if "message" in msguser
                            }

                            usermap = {
                                bin(node): msguser.get("user")
                                for (node, msguser) in msgusermap.items()
                                if "user" in msguser
                            }
                        except TypeError:
                            raise error.Abort(_("invalid JSON input"))

                commitopts["message"] = "\n".join(msgs)
                commitopts["edit"] = True

            if root == head:
                # fast path: use metarewrite
                replacemap = {}
                # adding commitopts to the revisions to metaedit
                allctxopt = [{
                    "ctx": ctx,
                    "commitopts": commitopts
                } for ctx in allctx]
                # all descendats that can be safely rewritten
                newunstable = common.newunstable(repo, revs)
                newunstableopt = [{
                    "ctx": ctx
                } for ctx in [repo[r] for r in newunstable]]
                # we need to edit descendants with the given revisions to not to
                # corrupt the stacks
                if _histediting(repo):
                    ui.note(
                        _("during histedit, the descendants of "
                          "the edited commit weren't auto-rebased\n"))
                else:
                    allctxopt += newunstableopt
                # we need topological order for all
                if mutation.enabled(repo):
                    allctxopt = mutation.toposort(
                        repo,
                        allctxopt,
                        nodefn=lambda copt: copt["ctx"].node())
                else:
                    allctxopt = sorted(allctxopt,
                                       key=lambda copt: copt["ctx"].rev())

                def _rewritesingle(c, _commitopts):
                    # Predefined message overrides other message editing choices.
                    msg = msgmap.get(c.node())
                    if jsoninputfile:
                        _commitopts["edit"] = False
                    if msg is not None:
                        _commitopts["message"] = msg
                        _commitopts["edit"] = False
                    user = usermap.get(c.node())
                    if user is not None:
                        _commitopts["user"] = user
                    if _commitopts.get("edit", False):
                        msg = "HG: Commit message of changeset %s\n%s" % (
                            str(c),
                            c.description(),
                        )
                        _commitopts["message"] = msg
                    bases = [
                        replacemap.get(c.p1().node(),
                                       c.p1().node()),
                        replacemap.get(c.p2().node(),
                                       c.p2().node()),
                    ]
                    newid, created = common.metarewrite(repo,
                                                        c,
                                                        bases,
                                                        commitopts=_commitopts)
                    if created:
                        replacemap[c.node()] = newid

                for copt in allctxopt:
                    _rewritesingle(
                        copt["ctx"],
                        copt.get("commitopts",
                                 {"date": commitopts.get("date") or None}),
                    )

                if p1.node() in replacemap:
                    repo.setparents(replacemap[p1.node()])
                if len(replacemap) > 0:
                    mapping = dict(
                        map(
                            lambda oldnew: (oldnew[0], [oldnew[1]]),
                            pycompat.iteritems(replacemap),
                        ))
                    templ.setprop("nodereplacements", mapping)
                    scmutil.cleanupnodes(repo, mapping, "metaedit")
                    # TODO: set poroper phase boundaries (affects secret
                    # phase only)
                else:
                    ui.status(_("nothing changed\n"))
                    return 1
            else:
                # slow path: create a new commit
                targetphase = max(c.phase() for c in allctx)

                # TODO: if the author and message are the same, don't create a
                # new hash. Right now we create a new hash because the date can
                # be different.
                newid, created = common.rewrite(
                    repo,
                    root,
                    allctx,
                    head,
                    [root.p1().node(), root.p2().node()],
                    commitopts=commitopts,
                    mutop="metaedit",
                )
                if created:
                    if p1.rev() in revs:
                        newp1 = newid
                    phases.retractboundary(repo, tr, targetphase, [newid])
                    mapping = dict([(repo[rev].node(), [newid])
                                    for rev in revs])
                    templ.setprop("nodereplacements", mapping)
                    scmutil.cleanupnodes(repo, mapping, "metaedit")
                else:
                    ui.status(_("nothing changed\n"))
                    return 1
            tr.close()
        finally:
            tr.release()

        if opts["fold"]:
            ui.status(_("%i changesets folded\n") % len(revs))
        if newp1 is not None:
            hg.update(repo, newp1)
示例#9
0
def expushdiscoverybookmarks(pushop):
    repo = pushop.repo

    if pushop.delete:
        remotemarks = pushop.remote.listkeyspatterns("bookmarks",
                                                     [pushop.delete])
        if pushop.delete not in remotemarks:
            raise error.Abort(
                _("remote bookmark %s does not exist") % pushop.delete)
        pushop.outbookmarks.append(
            [pushop.delete, remotemarks[pushop.delete], ""])
        return exchange._pushdiscoverybookmarks(pushop)

    if not pushop.to:
        ret = exchange._pushdiscoverybookmarks(pushop)
        if not pushop.allowanon:
            # check to make sure we don't push an anonymous head
            if pushop.revs:
                revs = set(pushop.revs)
            else:
                revs = set(repo.lookup(r) for r in repo.revs("head()"))
            revs -= set(pushop.remoteheads)
            # find heads that don't have a bookmark going with them
            for bookmark in pushop.bookmarks:
                rev = repo.lookup(bookmark)
                if rev in revs:
                    revs.remove(rev)
            # remove heads that advance bookmarks (old mercurial behavior)
            for bookmark, old, new in pushop.outbookmarks:
                rev = repo.lookup(new)
                if rev in revs:
                    revs.remove(rev)

            # we use known() instead of lookup() due to lookup throwing an
            # aborting error causing the connection to close
            anonheads = []
            revs = sorted(revs)
            knownlist = pushop.remote.known(revs)
            for node, known in zip(revs, knownlist):
                ctx = repo[node]
                if (known or ctx.obsolete() or ctx.closesbranch() or
                        # if there is a topic, let's just skip it for now
                    (ctx.mutable() and "topic" in ctx.extra())):
                    continue
                anonheads.append(short(node))

            if anonheads:
                msg = _("push would create new anonymous heads (%s)")
                hint = _("use --allow-anon to override this warning")
                raise error.Abort(msg % ", ".join(sorted(anonheads)),
                                  hint=hint)
        return ret

    # in this path, we have a push --to command
    if not len(pushop.bookmarks):
        # if there are no bookmarks, something went wrong. bail gracefully.
        raise error.Abort("no bookmark found to push")

    bookmark = pushop.bookmarks[0]
    rev = pushop.revs[0]

    # allow new bookmark only if --create is specified
    old = ""
    remotemarks = pushop.remote.listkeyspatterns("bookmarks", [bookmark])
    if bookmark in remotemarks:
        old = remotemarks[bookmark]
    elif not pushop.create:
        msg = _("not creating new remote bookmark")
        hint = _("use --create to create a new bookmark")
        raise error.Abort(msg, hint=hint)

    # allow non-fg bookmark move only if --non-forward-move is specified
    if not pushop.nonforwardmove and old != "":
        # the first check isn't technically about non-fg moves, but the non-fg
        # check relies on the old bm location being in the local repo
        if old not in repo:
            msg = _("remote bookmark revision is not in local repo")
            hint = _("pull and merge or rebase or use --non-forward-move")
            raise error.Abort(msg, hint=hint)
        if mutation.enabled(repo):
            foreground = mutation.foreground(repo, [repo.lookup(old)])
        else:
            foreground = obsutil.foreground(repo, [repo.lookup(old)])
        if repo[rev].node() not in foreground:
            msg = _("pushed rev is not in the foreground of remote bookmark")
            hint = _("use --non-forward-move flag to complete arbitrary moves")
            raise error.Abort(msg, hint=hint)
        if repo[old] == repo[rev]:
            repo.ui.status_err(
                _("remote bookmark already points at pushed rev\n"))
            return

    pushop.outbookmarks.append((bookmark, old, hex(rev)))
示例#10
0
文件: smartlog.py 项目: jsoref/eden
def rebasesuccessors(repo, ctx, **args):
    """Return all of the node's successors created as a result of rebase"""
    if mutation.enabled(repo):
        return ""
    rsnodes = list(modifysuccessors(ctx, "rebase"))
    return templatekw.showlist("rebasesuccessor", rsnodes, args)