hgext3rd/evolve/rewind.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Sun, 17 Jun 2018 01:04:20 +0200
changeset 3862 8d3eed113b77
parent 3861 bbe635dfd75c
child 3863 c31be22d1d90
permissions -rw-r--r--
rewind: add a message about obsolete changeset Not the best output, but useful to have the data. We can improve that later.

from __future__ import absolute_import

import collections
import hashlib

from mercurial import (
    error,
    obsolete,
    obsutil,
    scmutil,
)

from mercurial.i18n import _

from . import (
    exthelper,
    rewriteutil,
    compat,
)

eh = exthelper.exthelper()

# flag in obsolescence markers to link to identical version
identicalflag = 4

@eh.command(
    '^rewind',
    [('', 'to', [], _("rewind to these revision")),
     ('', 'as-divergence', None, _("preserve current latest successors")),
    ],
    _(''))
def rewind(ui, repo, **opts):
    """rewind stacks of changeset to a previous content

    This command can be used to restore stacks of changesets to an obsolete
    state, creating identical identical copies.

    The latest successors the obsolete changesets will be superseed by these
    new copies. This behavior can be disabled using `--as-divergence`, the
    current latest successors won't be affected and content-divergence will
    appears between them and the restored version of the obsolete changesets.
    """
    unfi = repo.unfiltered()

    if not opts.get('to'):
        raise error.Abort('no revision to rewind to')

    rewinded = scmutil.revrange(repo, opts.get('to'))

    successorsmap = collections.defaultdict(set)
    rewindmap = {}
    sscache = {}
    with repo.wlock(), repo.lock():
        if not opts['as_divergence']:
            for rev in rewinded:
                ctx = unfi[rev]
                ssets = obsutil.successorssets(repo, ctx.node(), sscache)
                if 1 < len(ssets):
                    msg = _('rewind confused by divergence on %s') % ctx
                    hint = _('solve divergence first or use "--as-divergence"')
                    raise error.Abort(msg, hint=hint)
                if ssets and ssets[0]:
                    for succ in ssets[0]:
                        successorsmap[succ].add(ctx.node())

        # Check that we can rewind these changesets
        with repo.transaction('rewind'):
            for rev in rewinded:
                ctx = unfi[rev]
                rewindmap[ctx.node()] = _revive_revision(unfi, rev)

            relationships = []
            cl = unfi.changelog
            for (source, dest) in sorted(successorsmap.items()):
                newdest = [rewindmap[d] for d in sorted(dest, key=cl.rev)]
                rel = (unfi[source], tuple(unfi[d] for d in newdest))
                relationships.append(rel)
            obsolete.createmarkers(unfi, relationships, operation='rewind')
    repo.ui.status(_('rewinded to %d changesets\n') % len(rewinded))
    if relationships:
        repo.ui.status(_('(%d changesets obsoleted)\n') % len(relationships))

def _revive_revision(unfi, rev):
    """rewind a single revision rev.
    """
    ctx = unfi[rev]
    extra = ctx.extra().copy()
    # rewind hash should be unique over multiple rewind.
    user = unfi.ui.config('devel', 'user.obsmarker')
    if not user:
        user = unfi.ui.username()
    date = unfi.ui.configdate('devel', 'default-date')
    if date is None:
        date = compat.makedate()
    noise = "%s\0%s\0%d\0%d" % (ctx.node(), user, date[0], date[1])
    extra['__rewind-hash__'] = hashlib.sha256(noise).hexdigest()

    p1 = ctx.p1().node()
    p2 = ctx.p2().node()

    extradict = {'extra': extra}

    new, unusedvariable = rewriteutil.rewrite(unfi, ctx, [], ctx,
                                              [p1, p2],
                                              commitopts=extradict)

    obsolete.createmarkers(unfi, [(ctx, (unfi[new],))],
                           flag=identicalflag, operation='rewind')

    return new