Ejemplo n.º 1
0
def prune(ui, repo, *revs, **opts):
    """hide changesets by marking them obsolete

    Pruned changesets are obsolete with no successors. If they also have no
    descendants, they are hidden (invisible to all commands).

    Non-obsolete descendants of pruned changesets become "unstable". Use
    :hg:`evolve` to handle this situation.

    When you prune the parent of your working copy, Mercurial updates the
    working copy to a non-obsolete parent.

    You can use ``--succ`` to tell Mercurial that a newer version (successor)
    of the pruned changeset exists. Mercurial records successor revisions in
    obsolescence markers.

    You can use the ``--biject`` option to specify a 1-1 mapping (bijection)
    between revisions to pruned (precursor) and successor changesets. This
    option may be removed in a future release (with the functionality provided
    automatically).

    If you specify multiple revisions in ``--succ``, you are recording a
    "split" and must acknowledge it by passing ``--split``. Similarly, when you
    prune multiple changesets with a single successor, you must pass the
    ``--fold`` option.
    """
    if opts.get("keep", False):
        hint = "strip-uncommit"
    else:
        hint = "strip-hide"
    hintutil.trigger(hint)

    revs = scmutil.revrange(repo, list(revs) + opts.get("rev", []))
    succs = opts.get("succ", [])
    bookmarks = set(opts.get("bookmark", ()))
    metadata = _getmetadata(**opts)
    biject = opts.get("biject")
    fold = opts.get("fold")
    split = opts.get("split")

    options = [o for o in ("biject", "fold", "split") if opts.get(o)]
    if 1 < len(options):
        raise error.Abort(_("can only specify one of %s") % ", ".join(options))

    if bookmarks:
        revs += bookmarksmod.reachablerevs(repo, bookmarks)
        if not revs:
            # No revs are reachable exclusively from these bookmarks, just
            # delete the bookmarks.
            with repo.wlock(), repo.lock(), repo.transaction(
                    "prune-bookmarks") as tr:
                bookmarksmod.delete(repo, tr, bookmarks)
            for bookmark in sorted(bookmarks):
                ui.write(_("bookmark '%s' deleted\n") % bookmark)
            return 0

    if not revs:
        raise error.Abort(_("nothing to prune"))

    wlock = lock = tr = None
    try:
        wlock = repo.wlock()
        lock = repo.lock()
        tr = repo.transaction("prune")
        # defines pruned changesets
        precs = []
        revs.sort()
        for p in revs:
            cp = repo[p]
            if not cp.mutable():
                # note: createmarkers() would have raised something anyway
                raise error.Abort(
                    "cannot prune immutable changeset: %s" % cp,
                    hint="see 'hg help phases' for details",
                )
            precs.append(cp)
        if not precs:
            raise error.Abort("nothing to prune")

        # defines successors changesets
        sucs = scmutil.revrange(repo, succs)
        sucs.sort()
        sucs = tuple(repo[n] for n in sucs)
        if not biject and len(sucs) > 1 and len(precs) > 1:
            msg = "Can't use multiple successors for multiple precursors"
            hint = _("use --biject to mark a series as a replacement"
                     " for another")
            raise error.Abort(msg, hint=hint)
        elif biject and len(sucs) != len(precs):
            msg = "Can't use %d successors for %d precursors" % (len(sucs),
                                                                 len(precs))
            raise error.Abort(msg)
        elif (len(precs) == 1 and len(sucs) > 1) and not split:
            msg = "please add --split if you want to do a split"
            raise error.Abort(msg)
        elif len(sucs) == 1 and len(precs) > 1 and not fold:
            msg = "please add --fold if you want to do a fold"
            raise error.Abort(msg)
        elif biject:
            relations = [(p, (s, )) for p, s in zip(precs, sucs)]
        else:
            relations = [(p, sucs) for p in precs]

        wdp = repo["."]

        if len(sucs) == 1 and len(precs) == 1 and wdp in precs:
            # '.' killed, so update to the successor
            newnode = sucs[0]
        else:
            # update to an unkilled parent
            newnode = wdp

            while newnode in precs or newnode.obsolete():
                newnode = newnode.parents()[0]

        if newnode.node() != wdp.node():
            if opts.get("keep", False):
                # This is largely the same as the implementation in
                # strip.stripcmd(). We might want to refactor this somewhere
                # common at some point.

                # only reset the dirstate for files that would actually change
                # between the working context and uctx
                descendantrevs = repo.revs("%d::." % newnode.rev())
                changedfiles = []
                for rev in descendantrevs:
                    # blindly reset the files, regardless of what actually
                    # changed
                    changedfiles.extend(repo[rev].files())

                # reset files that only changed in the dirstate too
                dirstate = repo.dirstate
                dirchanges = [f for f in dirstate if dirstate[f] != "n"]
                changedfiles.extend(dirchanges)
                repo.dirstate.rebuild(newnode.node(), newnode.manifest(),
                                      changedfiles)
                dirstate.write(tr)
            else:
                bookactive = repo._activebookmark
                # Active bookmark that we don't want to delete (with -B option)
                # we deactivate and move it before the update and reactivate it
                # after
                movebookmark = bookactive and not bookmarks
                if movebookmark:
                    bookmarksmod.deactivate(repo)
                    changes = [(bookactive, newnode.node())]
                    repo._bookmarks.applychanges(repo, tr, changes)
                commands.update(ui, repo, newnode.hex())
                ui.status(
                    _("working directory now at %s\n") %
                    ui.label(str(newnode), "evolve.node"))
                if movebookmark:
                    bookmarksmod.activate(repo, bookactive)

        # update bookmarks
        if bookmarks:
            with repo.wlock(), repo.lock(), repo.transaction(
                    "prune-bookmarks") as tr:
                bookmarksmod.delete(repo, tr, bookmarks)
            for bookmark in sorted(bookmarks):
                ui.write(_("bookmark '%s' deleted\n") % bookmark)

        # create markers
        obsolete.createmarkers(repo,
                               relations,
                               metadata=metadata,
                               operation="prune")

        # hide nodes
        visibility.remove(repo, [c.node() for c in precs])

        # informs that changeset have been pruned
        ui.status(_("%i changesets pruned\n") % len(precs))

        for ctx in repo.set("bookmark() and %ld", precs):
            # used to be:
            #
            #   ldest = list(repo.set('max((::%d) - obsolete())', ctx))
            #   if ldest:
            #      c = ldest[0]
            #
            # but then revset took a lazy arrow in the knee and became much
            # slower. The new forms makes as much sense and a much faster.
            for dest in ctx.ancestors():
                if not dest.obsolete():
                    updatebookmarks = common.bookmarksupdater(repo, ctx.node())
                    updatebookmarks(dest.node())
                    break

        tr.close()
    finally:
        lockmod.release(tr, lock, wlock)
Ejemplo n.º 2
0
def split(ui, repo, *revs, **opts):
    """split a changeset into smaller changesets

    Prompt for hunks to be selected until exhausted. Each selection of hunks
    will form a separate changeset, in order from parent to child: the first
    selection will form the first changeset, the second selection will form
    the second changeset, and so on.

    Operates on the current revision by default. Use --rev to split a given
    changeset instead.
    """
    newcommits = []

    revarg = (list(revs) + opts.get("rev")) or ["."]
    if len(revarg) != 1:
        msg = _("more than one revset is given")
        hnt = _(
            "use either `hg split <rs>` or `hg split --rev <rs>`, not both")
        raise error.Abort(msg, hint=hnt)

    rev = scmutil.revsingle(repo, revarg[0])
    if opts.get("no_rebase"):
        torebase = ()
    else:
        torebase = list(
            map(hex, repo.nodes("descendants(%d) - (%d)", rev, rev)))

    with repo.wlock(), repo.lock():
        cmdutil.bailifchanged(repo)
        if torebase:
            cmdutil.checkunfinished(repo)
        ctx = repo[rev]
        r = ctx.hex()
        allowunstable = visibility.tracking(repo) or obsolete.isenabled(
            repo, obsolete.allowunstableopt)
        if not allowunstable:
            # XXX We should check head revs
            if repo.revs("(%d::) - %d", rev, rev):
                raise error.Abort(
                    _("cannot split commit: %s not a head") % ctx)

        if len(ctx.parents()) > 1:
            raise error.Abort(_("cannot split merge commits"))
        prev = ctx.p1()
        bmupdate = common.bookmarksupdater(repo, ctx.node())
        bookactive = repo._activebookmark
        if bookactive is not None:
            repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
        bookmarks.deactivate(repo)
        hg.update(repo, prev)

        commands.revert(ui, repo, rev=r, all=True)

        def haschanges():
            modified, added, removed, deleted = repo.status()[:4]
            return modified or added or removed or deleted

        # We need to detect the case where the user selects all remaining
        # changes, as that will end the split.  That's the commit we want to
        # mark as the result of the split.  To do this, wrap the recordfilter
        # function and compare the output to see if it contains all the
        # originalchunks.
        shouldrecordmutation = [False]

        def mutinfo(extra):
            if shouldrecordmutation[0]:
                return mutation.record(
                    repo,
                    extra,
                    [ctx.node()],
                    "split",
                    splitting=[c.node() for c in newcommits],
                )

        def recordfilter(ui, originalchunks, operation=None):
            chunks, newopts = cmdutil.recordfilter(ui, originalchunks,
                                                   operation)
            if cmdutil.comparechunks(chunks, originalchunks):
                shouldrecordmutation[0] = True
            return chunks, newopts

        msg = ("HG: This is the original pre-split commit message. "
               "Edit it as appropriate.\n\n")
        msg += ctx.description()
        opts["message"] = msg
        opts["edit"] = True
        opts["_commitmutinfofunc"] = mutinfo
        try:
            while haschanges():
                pats = ()
                with repo.transaction("split"):
                    cmdutil.dorecord(ui, repo, commands.commit, "commit",
                                     False, recordfilter, *pats, **opts)
                # TODO: Does no seem like the best way to do this
                # We should make dorecord return the newly created commit
                newcommits.append(repo["."])
                if haschanges():
                    if ui.prompt("Done splitting? [yN]", default="n") == "y":
                        shouldrecordmutation[0] = True
                        with repo.transaction("split"):
                            commands.commit(ui, repo, **opts)
                        newcommits.append(repo["."])
                        break
                else:
                    ui.status(_("no more change to split\n"))
        except Exception:
            # Rollback everything
            hg.updaterepo(repo, r, True)  # overwrite=True
            if newcommits:
                visibility.remove(repo, [c.node() for c in newcommits])

            if bookactive is not None:
                bookmarks.activate(repo, bookactive)
            raise

        if newcommits:
            phabdiffs = {}
            for c in newcommits:
                phabdiff = diffprops.parserevfromcommitmsg(
                    repo[c].description())
                if phabdiff:
                    phabdiffs.setdefault(phabdiff, []).append(c)
            if any(len(commits) > 1 for commits in phabdiffs.values()):
                hintutil.trigger("split-phabricator",
                                 ui.config("split", "phabricatoradvice"))

            tip = repo[newcommits[-1]]
            with repo.transaction("post-split"):
                bmupdate(tip.node())
                if bookactive is not None:
                    bookmarks.activate(repo, bookactive)
                if obsolete.isenabled(repo, obsolete.createmarkersopt):
                    obsolete.createmarkers(repo, [(repo[r], newcommits)],
                                           operation="split")
            if torebase:
                rebaseopts = {"dest": "_destrestack(SRC)", "rev": torebase}
                rebase.rebase(ui, repo, **rebaseopts)
            unfi = repo
            with repo.transaction("post-split-hide"):
                visibility.remove(repo, [unfi[r].node()])
Ejemplo n.º 3
0
def _backupactivebookmark(repo):
    activebookmark = repo._activebookmark
    if activebookmark:
        bookmarks.deactivate(repo)
    return activebookmark