Skip to content
Snippets Groups Projects
rebase.py 57.6 KiB
Newer Older
Stefano Tortarolo's avatar
Stefano Tortarolo committed
# rebase.py - rebasing feature for mercurial
#
# Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
#
# This software may be used and distributed according to the terms of the
Matt Mackall's avatar
Matt Mackall committed
# GNU General Public License version 2 or any later version.
'''command to move sets of revisions to a different ancestor
This extension lets you rebase changesets in an existing Mercurial
repository.
Stefano Tortarolo's avatar
Stefano Tortarolo committed

For more information:
https://mercurial-scm.org/wiki/RebaseExtension
from __future__ import absolute_import

import errno
import os

from mercurial.i18n import _
from mercurial.node import (
    hex,
    nullid,
    nullrev,
    short,
)
from mercurial import (
    bookmarks,
    cmdutil,
    commands,
    copies,
    destutil,
    error,
    extensions,
    hg,
    lock,
    merge as mergemod,
    patch,
    phases,
    registrar,
    repair,
    repoview,
    revset,
    scmutil,
    util,
)

release = lock.release
templateopts = cmdutil.templateopts
# The following constants are used throughout the rebase module. The ordering of
# their values must be maintained.

# Indicates that a revision needs to be rebased
revtodo = -1
# successor in rebase destination
# plain prune (no successor)
revskipped = (revignored, revprecursor, revpruned)
cmdtable = {}
command = registrar.command(cmdtable)
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = 'ships-with-hg-core'
def _nothingtorebase():
    return 1

def _savegraft(ctx, extra):
    s = ctx.extra().get('source', None)
    if s is not None:
        extra['source'] = s
    s = ctx.extra().get('intermediate-source', None)
    if s is not None:
        extra['intermediate-source'] = s

def _savebranch(ctx, extra):
    extra['branch'] = ctx.branch()

def _makeextrafn(copiers):
    """make an extrafn out of the given copy-functions.

    A copy function takes a context and an extra dict, and mutates the
    extra dict as needed based on the given context.
    """
    def extrafn(ctx, extra):
        for c in copiers:
            c(ctx, extra)
    return extrafn

def _destrebase(repo, sourceset, destspace=None):
    """small wrapper around destmerge to pass the right extra args

    Please wrap destutil.destmerge instead."""
    return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
                              onheadcheck=False, destspace=destspace)
revsetpredicate = registrar.revsetpredicate()
def _revsetdestrebase(repo, subset, x):
    # ``_rebasedefaultdest()``

    # default destination for rebase.
    # # XXX: Currently private because I expect the signature to change.
    # # XXX: - bailing out in case of ambiguity vs returning all data.
    # i18n: "_rebasedefaultdest" is a keyword
        sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
    return subset & smartset.baseset([_destrebase(repo, sourceset)])
class rebaseruntime(object):
    """This class is a container for rebase runtime state"""
    def __init__(self, repo, ui, opts=None):
        if opts is None:
            opts = {}

        self.repo = repo
        self.ui = ui
        self.opts = opts
        self.originalwd = None
        self.external = nullrev
        # Mapping between the old revision id and either what is the new rebased
        # revision or what needs to be done with the old revision. The state
        # dict will be what contains most of the rebase progress state.
        self.state = {}
        self.activebookmark = None
        self.collapsef = opts.get('collapse', False)
        self.collapsemsg = cmdutil.logmessage(ui, opts)
        self.date = opts.get('date', None)

        e = opts.get('extrafn') # internal, used by e.g. hgsubversion
        self.extrafns = [_savegraft]
        if e:
            self.extrafns = [e]
        self.keepf = opts.get('keep', False)
        self.keepbranchesf = opts.get('keepbranches', False)
        # keepopen is not meant for use on the command line, but by
        # other extensions
        self.keepopen = opts.get('keepopen', False)
    def storestatus(self, tr=None):
        """Store the current status to allow recovery"""
        if tr:
            tr.addfilegenerator('rebasestate', ('rebasestate',),
                                self._writestatus, location='plain')
        else:
            with self.repo.vfs("rebasestate", "w") as f:
                self._writestatus(f)

    def _writestatus(self, f):
        repo = self.repo.unfiltered()
        f.write(repo[self.originalwd].hex() + '\n')
        f.write(repo[self.dest].hex() + '\n')
        f.write(repo[self.external].hex() + '\n')
        f.write('%d\n' % int(self.collapsef))
        f.write('%d\n' % int(self.keepf))
        f.write('%d\n' % int(self.keepbranchesf))
        f.write('%s\n' % (self.activebookmark or ''))
        for d, v in self.state.iteritems():
            oldrev = repo[d].hex()
            if v >= 0:
                newrev = repo[v].hex()
            elif v == revtodo:
                # To maintain format compatibility, we have to use nullid.
                # Please do remove this special case when upgrading the format.
                newrev = hex(nullid)
            else:
                newrev = v
            f.write("%s:%s\n" % (oldrev, newrev))
        repo.ui.debug('rebase status stored\n')

    def restorestatus(self):
        """Restore a previously stored status"""
        repo = self.repo
        keepbranches = None
        collapse = False
        external = nullrev
        activebookmark = None
Loading
Loading full blame...