Пример #1
0
def drop(ui, repo, *revs, **opts):
    """drop changeset from stack
    """
    if not rebasemod:
        raise error.Abort(_("required extensions not detected"))

    cmdutil.checkunfinished(repo)
    cmdutil.bailifchanged(repo)

    revs = scmutil.revrange(repo, list(revs) + opts.get("rev"))
    if not revs:
        raise error.Abort(_("no revision to drop was provided"))

    # currently drop supports dropping only one changeset at a time
    if len(revs) > 1:
        raise error.Abort(_("only one revision can be dropped at a time"))

    revid = revs.first()
    changectx = repo[revid]
    if changectx.phase() == phases.public:
        raise error.Abort(_("public changeset which landed cannot be dropped"))

    parents = repo.revs("parents(%s)", revid)
    if len(parents) > 1:
        raise error.Abort(_("merge changeset cannot be dropped"))
    elif len(parents) == 0:
        raise error.Abort(_("root changeset cannot be dropped"))

    _showrev(ui, repo, revid)

    descendants = repo.revs("(%d::) - %d", revid, revid)
    parent = parents.first()
    with repo.wlock():
        with repo.lock():
            with repo.transaction("drop"):
                if len(descendants) > 0:
                    try:
                        rebasemod.rebase(ui,
                                         repo,
                                         dest=str(parent),
                                         rev=descendants)
                    except error.InterventionRequired:
                        ui.warn(
                            _("conflict occurred during drop: " +
                              "please fix it by running " +
                              "'hg rebase --continue', " +
                              "and then re-run 'hg drop'\n"))
                        raise
                    scmutil.cleanupnodes(repo, [changectx.node()], "drop")
Пример #2
0
def _deleteunreachable(repo, ctx):
    """Deletes all ancestor and descendant commits of the given revision that
    aren't reachable from another bookmark.
    """
    keepheads = "bookmark() + ."
    try:
        extensions.find("remotenames")
        keepheads += " + remotenames()"
    except KeyError:
        pass
    hidenodes = list(repo.nodes("(draft() & ::%s) - ::(%r)", ctx.rev(), keepheads))
    if hidenodes:
        with repo.lock():
            scmutil.cleanupnodes(repo, hidenodes, "reset")
        repo.ui.status(
            _n("%d changeset hidden\n", "%d changesets hidden\n", len(hidenodes))
            % len(hidenodes)
        )
Пример #3
0
 def commit(self):
     """commit changes. update self.finalnode, self.replacemap"""
     with self.repo.wlock(), self.repo.lock():
         with self.repo.transaction("absorb"):
             self._commitstack()
             replacements = {
                 old: [new] if new else [] for old, new in self.replacemap.items()
             }
             moves = scmutil.cleanupnodes(self.repo, replacements, "absorb")
             newwd = moves.get(self.repo["."].node())
             if newwd is not None:
                 self._moveworkingdirectoryparent(newwd)
     return self.finalnode
Пример #4
0
def fold(ui, repo, *revs, **opts):
    """combine multiple commits into a single commit

    With --from, folds all the revisions linearly between the current revision
    and the specified revision.

    With --exact, folds only the specified revisions while ignoring the revision
    currently checked out. The given revisions must form a linear unbroken
    chain.

    .. container:: verbose

     Some examples:

     - Fold from the current revision to its parent::

         hg fold --from .^

     - Fold all draft revisions into the current revision::

         hg fold --from 'draft()'

       See :hg:`help phases` for more about draft revisions and
       :hg:`help revsets` for more about the `draft()` keyword

     - Fold revisions between 3 and 6 into the current revision::

         hg fold --from 3::6

     - Fold revisions 3 and 4:

        hg fold "3 + 4" --exact

     - Only fold revisions linearly between foo and @::

         hg fold foo::@ --exact
    """
    revs = list(revs)
    revs.extend(opts["rev"])
    if not revs:
        raise error.Abort(_("no revisions specified"))

    revs = scmutil.revrange(repo, revs)

    if opts.get("no_rebase"):
        torebase = ()
    else:
        torebase = repo.revs("descendants(%ld) - (%ld)", revs, revs)

    if opts["from"] and opts["exact"]:
        raise error.Abort(_("cannot use both --from and --exact"))
    elif opts["from"]:
        # Try to extend given revision starting from the working directory
        extrevs = repo.revs("(%ld::.) or (.::%ld)", revs, revs)
        discardedrevs = [r for r in revs if r not in extrevs]
        if discardedrevs:
            msg = _("cannot fold non-linear revisions")
            hint = _("given revisions are unrelated to parent of working"
                     " directory")
            raise error.Abort(msg, hint=hint)
        revs = extrevs
    elif opts["exact"]:
        # Nothing to do; "revs" is already set correctly
        pass
    else:
        raise error.Abort(_("must specify either --from or --exact"))

    if not revs:
        raise error.Abort(
            _("specified revisions evaluate to an empty set"),
            hint=_("use different revision arguments"),
        )
    elif len(revs) == 1:
        ui.write_err(_("single revision specified, nothing to fold\n"))
        return 1

    with repo.wlock(), repo.lock(), ui.formatter("fold", opts) as fm:
        fm.startitem()
        root, head = _foldcheck(repo, revs)

        with repo.transaction("fold") as tr:
            commitopts = opts.copy()
            allctx = [repo[r] for r in revs]
            targetphase = max(c.phase() for c in allctx)

            if (commitopts.get("message") or commitopts.get("logfile")
                    or commitopts.get("reuse_message")):
                commitopts["edit"] = False
            else:
                msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
                msgs += [
                    "HG: Commit message of %s.\n\n%s\n" %
                    (node.short(c.node()), c.description()) for c in allctx
                ]
                commitopts["message"] = "\n".join(msgs)
                commitopts["edit"] = True

            newid, unusedvariable = common.rewrite(
                repo,
                root,
                allctx,
                head,
                [root.p1().node(), root.p2().node()],
                commitopts=commitopts,
                mutop="fold",
            )
            phases.retractboundary(repo, tr, targetphase, [newid])

            replacements = {ctx.node(): (newid, ) for ctx in allctx}
            nodechanges = {
                fm.hexfunc(ctx.node()): [fm.hexfunc(newid)]
                for ctx in allctx
            }
            fm.data(nodechanges=fm.formatdict(nodechanges))
            scmutil.cleanupnodes(repo, replacements, "fold")
            fm.condwrite(not ui.quiet, "count", "%i changesets folded\n",
                         len(revs))
            if repo["."].rev() in revs:
                hg.update(repo, newid)

            if torebase:
                common.restackonce(ui, repo, repo[newid].rev())
Пример #5
0
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)
Пример #6
0
def amendtocommit(ui, repo, commitspec, pats=None, opts=None):
    """amend to a specific commit

    This works by patching the working diff on to the specified commit
    and then performing a simplified rebase of the stack's tail on to
    the amended ancestor.

    commitspec must refer to a single commit that is a linear ancestor
    of ".".
    """
    with repo.wlock(), repo.lock(), repo.transaction("amend"):
        revs = list(scmutil.revrange(repo, [commitspec]))
        if len(revs) != 1:
            raise error.Abort(
                _("'%s' must refer to a single changeset") % commitspec)

        draftctxs = list(repo.revs("(%d)::.", revs[0]).iterctx())
        if len(draftctxs) == 0:
            raise error.Abort(
                _("revision '%s' is not an ancestor of the working copy") %
                commitspec)

        if repo.revs("%ld & merge()", draftctxs):
            raise error.Abort(_("cannot amend non-linear stack"))

        dest = draftctxs.pop(0)
        if dest.phase() == phases.public:
            raise error.Abort(_("cannot amend public changesets"))

        # Generate patch from wctx and apply to dest commit.
        mergedctx = mirrorwithmetadata(dest, "amend")
        wctx = repo[None]
        matcher = scmutil.match(wctx, pats, opts) if pats or opts else None

        store = patch.mempatchstore(mergedctx)
        backend = patch.mempatchbackend(ui, mergedctx, store)
        ret = patch.applydiff(
            ui,
            io.BytesIO(b"".join(list(wctx.diff(match=matcher, opts=opts)))),
            backend,
            store,
        )
        if ret < 0:
            raise error.Abort(
                _("amend would conflict in %s") % ", ".join(backend.rejs))

        memctxs = [mergedctx]
        mappednodes = [dest.node()]

        # Perform mini-rebase of our stack.
        for ctx in draftctxs:
            memctxs.append(inmemorymerge(ui, repo, ctx, memctxs[-1], ctx.p1()))
            mappednodes.append(ctx.node())

        parentnode = None
        mapping = {}
        # Execute our list of in-memory commits, updating descendants'
        # parent as we go.
        for i, memctx in enumerate(memctxs):
            if i > 0:
                memctx = context.memctx.mirror(memctx,
                                               parentnodes=(parentnode,
                                                            nullid))
            parentnode = memctx.commit()
            mapping[mappednodes[i]] = (parentnode, )

        scmutil.cleanupnodes(repo, {dest.node(): mapping.pop(dest.node())},
                             "amend")
        scmutil.cleanupnodes(repo, mapping, "rebase")

        with repo.dirstate.parentchange():
            # Update dirstate status of amended files.
            repo.dirstate.rebuild(parentnode,
                                  repo[parentnode].manifest(),
                                  wctx.files(),
                                  exact=True)