Move upgrade code to bzr-foreign.
authorJelmer Vernooij <jelmer@samba.org>
Tue, 9 Sep 2008 12:46:48 +0000 (14:46 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Tue, 9 Sep 2008 12:46:48 +0000 (14:46 +0200)
1  2 
TODO
__init__.py
errors.py
foreign/upgrade.py
tests/mapping_implementations/test_branch.py
tests/test_upgrade.py
upgrade.py

diff --cc TODO
index 5f2f35db2b43427303ba41e274d921d501448e9a,2d3f16c7c5827fdc87f9a197d1397b7ece0a9767..8638b8656955204e9cb5eb8b63dab51e47112f10
--- 1/TODO
--- 2/TODO
+++ b/TODO
@@@ -1,21 -1,1 +1,23 @@@
 -- Import CommitBuilder-based fetcher
 +mappingv4:
 + - implement layout functions, including command
 + - create schemes from layouts rather than the other way around
 +  + remove Mapping.get_guessed_layout()
 +- fix update_after_commit test
 +- fix text parents
 +
++move upgrade to bzr-foreign
++
 +more tests:
 +- Run all tests against repository with revprop changing allowed and without
 +- performance tests (count roundtrips?)
 +- bzr:hidden
 +- bzr svn-set-revprops
 +
 +performance:
 +- generate deltas rather than fulltexts when creating file id maps
 +- use replay and replay range to fetch revisions
 +- implement SvnWorkingTree.apply_inventory_delta()
 +
 +for other things to do, see:
 +
 +* https://bugs.launchpad.net/bzr-svn
diff --cc __init__.py
index 7c8a94d82d1ec2496ae6384314b39e9b0d5cb8f8,119edbb6cfa56142d7241622074535701032b1a3..5b0a5e9cca7cfb143c1a990135c37c2ab4016357
  # along with this program; if not, write to the Free Software
  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  
 -"""Foreign branch utilities."""
 +"""Support for Subversion branches
 +
 +Bazaar can be used with Subversion branches through the bzr-svn plugin.
 +
 +Most Bazaar commands should work fine with Subversion branches. To 
 +create new branches in Subversion using push, it is currently necessary
 +to use the svn-push command rather than the standard push command.
 +
 +bzr-svn also adds two new commands to Bazaar:
 +
 + - bzr svn-import
 + - bzr dpush
 +
 +For more information about bzr-svn, see the bzr-svn FAQ.
 +
 +"""
 +import bzrlib
 +from bzrlib import bzrdir, log, repository
 +from bzrlib.bzrdir import BzrDirFormat
 +from bzrlib.errors import BzrError
 +from bzrlib.commands import Command, register_command, display_command
 +from bzrlib.help_topics import topic_registry
 +from bzrlib.option import Option, RegistryOption
 +from bzrlib.revisionspec import SPEC_TYPES
 +from bzrlib.trace import warning, mutter
 +from bzrlib.transport import register_lazy_transport, register_transport_proto
 +
 +import os
  
 -from bzrlib import errors, registry
 -from bzrlib.commands import Command, Option
 +# versions ending in 'exp' mean experimental mappings
 +# versions ending in 'dev' mean development version
 +# versions ending in 'final' mean release (well tested, etc)
 +version_info = (0, 5, 0, 'exp', 0)
  
 +if version_info[3] == 'final':
 +    version_string = '%d.%d.%d' % version_info[:3]
 +else:
 +    version_string = '%d.%d.%d%s%d' % version_info
 +__version__ = version_string
  
 -class VcsMapping(object):
 -    """Describes the mapping between the semantics of Bazaar and a foreign vcs.
 +COMPATIBLE_BZR_VERSIONS = [(1, 6)]
  
 +def check_bzrlib_version(desired):
 +    """Check that bzrlib is compatible.
 +
 +    If version is < all compatible version, assume incompatible.
 +    If version is compatible version + 1, assume compatible, with deprecations
 +    Otherwise, assume incompatible.
      """
 -    experimental = False
 -    roundtripping = False
 -    revid_prefix = None
 +    bzrlib_version = bzrlib.version_info[:2]
 +    if (bzrlib_version in desired or 
 +        ((bzrlib_version[0], bzrlib_version[1]-1) in desired and 
 +         bzrlib.version_info[3] in ('dev', 'exp'))):
 +        return
 +    if bzrlib_version < desired[0]:
 +        raise BzrError('Installed bzr version %s is too old to be used with bzr-svn, at least %s.%s required' % (bzrlib.__version__, desired[0][0], desired[0][1]))
 +    else:
 +        warning('bzr-svn is not up to date with installed bzr version %s.'
 +                ' \nThere should be a newer version of bzr-svn available.',
 +                bzrlib.__version__)
 +        if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
 +            raise BzrError('Version mismatch')
 +
 +def check_subversion_version():
 +    """Check that Subversion is compatible.
 +
 +    """
 +    def check_mtime(m):
 +        """Check whether a C extension is out of date."""
 +        (base, _) = os.path.splitext(m.__file__)
 +        c_file = "%s.c" % base
 +        if not os.path.exists(c_file):
 +            return True
 +        if os.path.getmtime(m.__file__) < os.path.getmtime(c_file):
 +            return False
 +        return True
 +    try:
 +        from bzrlib.plugins.svn import client, ra, repos, wc
 +        for x in client, ra, repos, wc:
 +            if not check_mtime(x):
 +                warning("bzr-svn extensions are outdated and need to be rebuilt")
 +                break
 +    except ImportError:
 +        warning("Unable to load bzr-svn extensions - did you build it?")
 +        raise
 +    ra_version = ra.version()
 +    if (ra_version[0] >= 5 and getattr(ra, 'SVN_REVISION', None) and 
 +        27729 <= ra.SVN_REVISION < 31470):
 +        warning('Installed Subversion has buggy svn.ra.get_log() '
 +                'implementation, please install newer.')
 +
 +    mutter("bzr-svn: using Subversion %d.%d.%d (%s)" % ra_version)
 +
 +
- def check_rebase_version(min_version):
-     """Check what version of bzr-rebase is installed.
-     Raises an exception when the version installed is older than 
-     min_version.
-     :raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
-     """
-     from bzrlib.plugins.svn.errors import RebaseNotPresent
-     try:
-         from bzrlib.plugins.rebase import version_info as rebase_version_info
-         if rebase_version_info[:2] < min_version:
-             raise RebaseNotPresent("Version %r present, at least %r required" 
-                                    % (rebase_version_info, min_version))
-     except ImportError, e:
-         raise RebaseNotPresent(e)
 +check_subversion_version()
 +
 +from bzrlib.plugins.svn import format, revspec
 +
 +register_transport_proto('svn+ssh://', 
 +    help="Access using the Subversion smart server tunneled over SSH.")
 +register_transport_proto('svn+file://', 
 +    help="Access of local Subversion repositories.")
 +register_transport_proto('svn+http://',
 +    help="Access of Subversion smart servers over HTTP.")
 +register_transport_proto('svn+https://',
 +    help="Access of Subversion smart servers over secure HTTP.")
 +register_transport_proto('svn://', 
 +    help="Access using the Subversion smart server.")
 +register_lazy_transport('svn://', 'bzrlib.plugins.svn.transport', 
 +                        'SvnRaTransport')
 +register_lazy_transport('svn+', 'bzrlib.plugins.svn.transport', 
 +                        'SvnRaTransport')
 +topic_registry.register_lazy('svn-branching-schemes', 
 +                             'bzrlib.plugins.svn.mapping3.scheme',
 +                             'help_schemes', 'Subversion branching schemes')
 +
 +BzrDirFormat.register_control_format(format.SvnRemoteFormat)
 +BzrDirFormat.register_control_format(format.SvnWorkingTreeDirFormat)
 +bzrdir.format_registry.register("subversion", format.SvnRemoteFormat, 
 +                         "Subversion repository. ", 
 +                         native=False)
 +bzrdir.format_registry.register("subversion-wc", format.SvnWorkingTreeDirFormat, 
 +                         "Subversion working copy. ", 
 +                         native=False, hidden=True)
 +SPEC_TYPES.append(revspec.RevisionSpec_svn)
  
 -    def revision_id_bzr_to_foreign(self, bzr_revid):
 -        """Parse a bzr revision id and convert it to a foreign revid.
 +log.properties_handler_registry.register_lazy("subversion",
 +                                              "bzrlib.plugins.svn.log",
 +                                              "show_subversion_properties")
  
 -        :param bzr_revid: The bzr revision id (a string).
 -        :return: A foreign revision id, can be any sort of object.
 -        """
 -        raise NotImplementedError(self.revision_id_bzr_to_foreign)
 +_versions_checked = False
 +def lazy_check_versions():
 +    """Check whether all dependencies have the right versions.
 +    
 +    :note: Only checks once, caches the result."""
 +    global _versions_checked
 +    if _versions_checked:
 +        return
 +    _versions_checked = True
 +    check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
 +
 +_optimizers_registered = False
 +def lazy_register_optimizers():
 +    """Register optimizers for fetching between Subversion and Bazaar 
 +    repositories.
 +    
 +    :note: Only registers on the first call."""
 +    global _optimizers_registered
 +    if _optimizers_registered:
 +        return
 +    from bzrlib.repository import InterRepository
 +    from bzrlib.plugins.svn import commit, fetch
 +    _optimizers_registered = True
 +    InterRepository.register_optimiser(fetch.InterFromSvnRepository)
 +    InterRepository.register_optimiser(commit.InterToSvnRepository)
  
 -    def revision_id_foreign_to_bzr(self, foreign_revid):
 -        """Parse a foreign revision id and convert it to a bzr revid.
  
 -        :param foreign_revid: Foreign revision id, can be any sort of object.
 -        :return: A bzr revision id.
 -        """
 -        raise NotImplementedError(self.revision_id_foreign_to_bzr)
 +def get_layout(layoutname):
 +    """Parse layout name and return a layout.
 +    
 +    :param layout: Name of the layout to retrieve.
 +    """
 +    if isinstance(layoutname, unicode):
 +        layoutname = layoutname.encode("ascii")
 +    from bzrlib.plugins.svn.layout import layout_registry
 +    from bzrlib.errors import BzrCommandError
 +    
 +    try:
 +        ret = layout_registry.get(layoutname)
 +    except KeyError:
 +        raise BzrCommandError('No such repository layout %r' % layoutname)
 +    return ret
  
  
 -class VcsMappingRegistry(registry.Registry):
 -    """Registry for Bazaar<->foreign VCS mappings.
 +class cmd_svn_import(Command):
 +    """Convert a Subversion repository to a Bazaar repository.
      
 -    There should be one instance of this registry for every foreign VCS.
 +    To save disk space, only branches will be created by default 
 +    (no working trees). To create a tree for a branch, run "bzr co" in 
 +    it.
      """
 +    takes_args = ['from_location', 'to_location?']
 +    takes_options = [Option('trees', help='Create working trees.'),
 +                     Option('standalone', help='Create standalone branches.'),
 +                     Option('all', 
 +                         help='Convert all revisions, even those not in '
 +                              'current branch history (forbids --standalone).'),
 +                     Option('layout', type=get_layout,
 +                         help='Repository layout (none, trunk, etc). '
 +                              'Default: auto.'),
 +                     Option('keep', 
 +                         help="Don't delete branches removed in Subversion."),
 +                     Option('incremental',
 +                         help="Import revisions incrementally."),
 +                     Option('prefix', type=str, 
 +                         help='Only consider branches of which path starts '
 +                              'with prefix.')
 +                    ]
 +
 +    @display_command
 +    def run(self, from_location, to_location=None, trees=False, 
 +            standalone=False, layout=None, all=False, prefix=None, keep=False,
 +            incremental=False):
 +        from bzrlib.bzrdir import BzrDir
 +        from bzrlib.errors import BzrCommandError, NoRepositoryPresent
 +        from bzrlib import osutils, urlutils
 +        from bzrlib.plugins.svn.convert import convert_repository
 +        from bzrlib.plugins.svn.layout import repository_guess_layout
 +        from bzrlib.plugins.svn.repository import SvnRepository
 +
 +        if to_location is None:
 +            to_location = os.path.basename(from_location.rstrip("/\\"))
 +
 +        if all:
 +            # All implies shared repository 
 +            # (otherwise there is no repository to store revisions in)
 +            standalone = False
  
 -    def register(self, key, factory, help):
 -        """Register a mapping between Bazaar and foreign VCS semantics.
 +        if os.path.isfile(from_location):
 +            from bzrlib.plugins.svn.convert import load_dumpfile
 +            import tempfile
 +            tmp_repos = tempfile.mkdtemp(prefix='bzr-svn-dump-')
 +            load_dumpfile(from_location, tmp_repos)
 +            from_location = tmp_repos
 +        else:
 +            tmp_repos = None
  
 -        The factory must be a callable that takes one parameter: the key.
 -        It must produce an instance of VcsMapping when called.
 -        """
 -        registry.Registry.register(self, key, factory, help)
 +        from_dir = BzrDir.open(from_location)
 +        try:
 +            from_repos = from_dir.open_repository()
 +        except NoRepositoryPresent, e:
 +            if prefix is not None:
 +                raise BzrCommandError("Path inside repository specified "
 +                                      "and --prefix specified")
 +            from_repos = from_dir.find_repository()
 +            prefix = urlutils.relative_url(from_repos.base, from_location)
 +            prefix = prefix.encode("utf-8")
 +
 +        from_repos.lock_read()
 +        try:
 +            (guessed_overall_layout, _) = repository_guess_layout(from_repos, 
 +                from_repos.get_latest_revnum())
 +
 +            if prefix is not None:
 +                prefix = prefix.strip("/") + "/"
 +                if guessed_overall__layout.is_branch(prefix):
 +                    raise BzrCommandError("%s appears to contain a branch. " 
 +                            "For individual branches, use 'bzr branch'." % 
 +                            from_location)
 +                elif guessed_overall_layout.is_branch_parent(prefix):
 +                    self.outf.write("Importing branches with prefix /%s\n" % 
 +                        urlutils.unescape_for_display(prefix, self.outf.encoding))
 +                else:
 +                    raise BzrCommandError("The specified path is inside a branch. "
 +                        "Specify a different URL or a different repository layout.")
 +
 +            if not isinstance(from_repos, SvnRepository):
 +                raise BzrCommandError(
 +                        "Not a Subversion repository: %s" % from_location)
  
 -    def set_default(self, key):
 -        """Set the 'default' key to be a clone of the supplied key.
 +            def filter_branch(branch):
 +                if (prefix is not None and 
 +                    not branch.get_branch_path().startswith(prefix)):
 +                    return False
 +                return True
  
 -        This method must be called once and only once.
 -        """
 -        self._set_default_key(key)
 +            convert_repository(from_repos, to_location, layout, 
 +                               not standalone, trees, all, 
 +                               filter_branch=filter_branch,
 +                               keep=keep, incremental=incremental)
  
 -    def get_default(self):
 -        """Convenience function for obtaining the default mapping to use."""
 -        return self.get(self._get_default_key())
 +            if tmp_repos is not None:
 +                osutils.rmtree(tmp_repos)
 +        finally:
 +            from_repos.unlock()
  
  
 -class FakeControlFiles(object):
 -    """Dummy implementation of ControlFiles.
 +register_command(cmd_svn_import)
 +
 +class cmd_svn_upgrade(Command):
 +    """Upgrade revisions mapped from Subversion in a Bazaar branch.
      
 -    This is required as some code relies on controlfiles being 
 -    available."""
 -    def get_utf8(self, name):
 -        raise errors.NoSuchFile(name)
 +    This will change the revision ids of revisions whose parents 
 +    were mapped from svn revisions.
 +    """
 +    from bzrlib.plugins.svn.mapping import mapping_registry
 +    takes_args = ['from_repository?']
 +    takes_options = ['verbose', RegistryOption('mapping', 
 +                                 help="New mapping to upgrade to.",
 +                                 registry=mapping_registry,
 +                                 title="Subversion mapping",
 +                                 value_switches=True)]
  
 -    def get(self, name):
 -        raise errors.NoSuchFile(name)
 +    @display_command
 +    def run(self, from_repository=None, verbose=False, mapping=None):
-         from bzrlib.plugins.svn.upgrade import (upgrade_branch, 
++        from bzrlib.plugins.svn.mapping import mapping_registry
++        from bzrlib.plugins.svn.foreign.upgrade import (upgrade_branch, 
 +                                                upgrade_workingtree)
 +        from bzrlib.branch import Branch
 +        from bzrlib.errors import NoWorkingTree, BzrCommandError
 +        from bzrlib.repository import Repository
 +        from bzrlib.trace import info
 +        from bzrlib.workingtree import WorkingTree
 +        try:
 +            wt_to = WorkingTree.open(".")
 +            branch_to = wt_to.branch
 +        except NoWorkingTree:
 +            wt_to = None
 +            branch_to = Branch.open(".")
 +
 +        stored_loc = branch_to.get_parent()
 +        if from_repository is None:
 +            if stored_loc is None:
 +                raise BzrCommandError("No pull location known or"
 +                                             " specified.")
 +            else:
 +                import bzrlib.urlutils as urlutils
 +                display_url = urlutils.unescape_for_display(stored_loc,
 +                        self.outf.encoding)
 +                self.outf.write("Using saved location: %s\n" % display_url)
 +                from_repository = Branch.open(stored_loc).repository
 +        else:
 +            from_repository = Repository.open(from_repository)
 +
 +        if mapping is None:
 +            mapping = mapping_registry.get_default()
 +
 +        new_mapping = mapping.from_repository(from_repository)
 +
 +        if wt_to is not None:
 +            renames = upgrade_workingtree(wt_to, from_repository, 
 +                                          new_mapping=new_mapping,
++                                          mapping_registry=mapping_registry,
 +                                          allow_changes=True, verbose=verbose)
 +        else:
 +            renames = upgrade_branch(branch_to, from_repository, 
 +                                     new_mapping=new_mapping,
++                                     mapping_registry=mapping_registry,
 +                                     allow_changes=True, verbose=verbose)
  
 -    def break_lock(self):
 -        pass
 +        if renames == {}:
 +            info("Nothing to do.")
  
 +        if wt_to is not None:
 +            wt_to.set_last_revision(branch_to.last_revision())
  
 -class cmd_dpush(Command):
 -    """Push diffs into a foreign version control system without any 
 -    Bazaar-specific metadata.
 +register_command(cmd_svn_upgrade)
  
 -    This will afterwards rebase the local Bazaar branch on the remote
 -    branch unless the --no-rebase option is used, in which case 
 -    the two branches will be out of sync. 
 +class cmd_svn_push(Command):
 +    """Push revisions to Subversion, creating a new branch if necessary.
 +
 +    The behaviour of this command is the same as that of "bzr push", except 
 +    that it also creates new branches.
 +    
 +    This command is experimental and will be removed in the future when all 
 +    functionality is included in "bzr push".
      """
      takes_args = ['location?']
 -    takes_options = ['remember', Option('directory',
 +    takes_options = ['revision', 'remember', Option('directory',
              help='Branch to push from, '
                   'rather than the one containing the working directory.',
              short_name='d',
diff --cc errors.py
index b725760eef0329787372e7bb5e4fbf93d2262b35,0000000000000000000000000000000000000000..b85b9df2a8a1815f8e6facb61750fc8a5547f1b0
mode 100644,000000..100644
--- /dev/null
+++ b/errors.py
@@@ -1,216 -1,0 +1,209 @@@
- class RebaseNotPresent(DependencyNotPresent):
-     _fmt = "Unable to import bzr-rebase (required for svn-upgrade support): %(error)s"
-     def __init__(self, error):
-         DependencyNotPresent.__init__(self, 'bzr-rebase', error)
 +# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
 +
 +# This program is free software; you can redistribute it and/or modify
 +# it under the terms of the GNU General Public License as published by
 +# the Free Software Foundation; either version 3 of the License, or
 +# (at your option) any later version.
 +
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU General Public License for more details.
 +
 +# You should have received a copy of the GNU General Public License
 +# along with this program; if not, write to the Free Software
 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +"""Subversion-specific errors and conversion of Subversion-specific errors."""
 +
 +from bzrlib.errors import (BzrError, ConnectionError, ConnectionReset, 
 +                           LockError, NotBranchError, PermissionDenied, 
 +                           DependencyNotPresent, NoRepositoryPresent,
 +                           TransportError, UnexpectedEndOfContainerError,
 +                           NoSuchRevision)
 +
 +import urllib
 +from bzrlib.plugins.svn import core
 +
 +
 +ERR_UNKNOWN_HOSTNAME = 670002
 +ERR_UNKNOWN_HOSTNAME = 670002
 +ERR_RA_SVN_CONNECTION_CLOSED = 210002
 +ERR_WC_LOCKED = 155004
 +ERR_RA_NOT_AUTHORIZED = 170001
 +ERR_INCOMPLETE_DATA = 200003
 +ERR_RA_SVN_MALFORMED_DATA = 210004
 +ERR_RA_NOT_IMPLEMENTED = 170003
 +ERR_FS_NO_SUCH_REVISION = 160006
 +ERR_FS_TXN_OUT_OF_DATE = 160028
 +ERR_REPOS_DISABLED_FEATURE = 165006
 +ERR_STREAM_MALFORMED_DATA = 140001
 +ERR_RA_ILLEGAL_URL = 170000
 +ERR_RA_LOCAL_REPOS_OPEN_FAILED = 180001
 +ERR_BAD_URL = 125002
 +ERR_RA_DAV_REQUEST_FAILED = 175002
 +ERR_RA_DAV_PATH_NOT_FOUND = 175007
 +ERR_FS_NOT_DIRECTORY = 160016
 +ERR_FS_NOT_FOUND = 160013
 +ERR_FS_ALREADY_EXISTS = 160020
 +ERR_RA_SVN_REPOS_NOT_FOUND = 210005
 +ERR_WC_NOT_DIRECTORY = 155007
 +ERR_ENTRY_EXISTS = 150002
 +ERR_WC_PATH_NOT_FOUND = 155010
 +ERR_CANCELLED = 200015
 +ERR_WC_UNSUPPORTED_FORMAT = 155021
 +ERR_UNKNOWN_CAPABILITY = 200026
 +ERR_AUTHN_NO_PROVIDER = 215001
 +ERR_RA_DAV_RELOCATED = 175011
 +ERR_FS_NOT_FILE = 160017
 +ERR_WC_BAD_ADM_LOG = 155009
 +
 +
 +class InvalidBzrSvnRevision(NoSuchRevision):
 +    _fmt = """Revision id %(revid)s was added incorrectly"""
 +
 +    def __init__(self, revid):
 +        self.revid = revid
 +
 +
 +class NotSvnBranchPath(NotBranchError):
 +    """Error raised when a path was specified that did not exist."""
 +    _fmt = """%(path)s is not a valid Subversion branch path. 
 +See 'bzr help svn-repository-layout' for details."""
 +
 +    def __init__(self, branch_path, mapping=None):
 +        NotBranchError.__init__(self, urllib.quote(branch_path))
 +        self.mapping = mapping
 +
 +
 +class NoSvnRepositoryPresent(NoRepositoryPresent):
 +
 +    def __init__(self, url):
 +        BzrError.__init__(self)
 +        self.path = url
 +
 +
 +class ChangesRootLHSHistory(BzrError):
 +    _fmt = """changing lhs branch history not possible on repository root"""
 +
 +
 +class MissingPrefix(BzrError):
 +    _fmt = """Prefix missing for %(path)s; please create it before pushing. """
 +
 +    def __init__(self, path, existing_path):
 +        BzrError.__init__(self)
 +        self.path = path
 +        self.existing_path = existing_path
 +
 +
 +class RaRequestFailed(BzrError):
 +    _fmt = """A Subversion remote access command failed: %(message)"""
 +
 +    def __init__(self, message):
 +        BzrError.__init__(self)
 +        self.mesage = message
 +
 +
 +class RevpropChangeFailed(BzrError):
 +    _fmt = """Unable to set revision property %(name)s."""
 +
 +    def __init__(self, name):
 +        BzrError.__init__(self)
 +        self.name = name
 +
 +
 +class DavRequestFailed(BzrError):
 +    _fmt = """%(msg)s"""
 +
 +    def __init__(self, msg):
 +        BzrError.__init__(self)
 +        self.msg = msg
 +
 +
 +def convert_error(err):
 +    """Convert a Subversion exception to the matching BzrError.
 +
 +    :param err: SubversionException.
 +    :return: BzrError instance if it could be converted, err otherwise
 +    """
 +    (msg, num) = err.args
 +
 +    if num == ERR_RA_SVN_CONNECTION_CLOSED:
 +        return ConnectionReset(msg=msg)
 +    elif num == ERR_WC_LOCKED:
 +        return LockError(message=msg)
 +    elif num == ERR_RA_NOT_AUTHORIZED:
 +        return PermissionDenied('.', msg)
 +    elif num == ERR_INCOMPLETE_DATA:
 +        return UnexpectedEndOfContainerError()
 +    elif num == ERR_RA_SVN_MALFORMED_DATA:
 +        return TransportError("Malformed data", msg)
 +    elif num == ERR_RA_NOT_IMPLEMENTED:
 +        return NotImplementedError("Function not implemented in remote server")
 +    elif num == ERR_RA_DAV_REQUEST_FAILED:
 +        return RaRequestFailed(msg)
 +    elif num == ERR_UNKNOWN_HOSTNAME:
 +        return ConnectionError(msg=msg)
 +    elif num == ERR_RA_DAV_REQUEST_FAILED:
 +        return DavRequestFailed(msg)
 +    else:
 +        return err
 +
 +
 +def convert_svn_error(unbound):
 +    """Decorator that catches particular Subversion exceptions and 
 +    converts them to Bazaar exceptions.
 +    """
 +    def convert(*args, **kwargs):
 +        try:
 +            return unbound(*args, **kwargs)
 +        except core.SubversionException, e:
 +            raise convert_error(e)
 +
 +    convert.__doc__ = unbound.__doc__
 +    convert.__name__ = unbound.__name__
 +    return convert
 +
 +
 +class InvalidPropertyValue(BzrError):
 +    _fmt = 'Invalid property value for Subversion property %(property)s: %(msg)s'
 +
 +    def __init__(self, property, msg):
 +        BzrError.__init__(self)
 +        self.property = property
 +        self.msg = msg
 +
 +class InvalidFileName(BzrError):
 +    _fmt = "Unable to convert Subversion path %(path)s because it contains characters invalid in Bazaar."
 +
 +    def __init__(self, path):
 +        BzrError.__init__(self)
 +        self.path = path
 +
 +
 +class CorruptMappingData(BzrError):
 +    _fmt = """An invalid change was made to the bzr-specific properties in %(path)s."""
 +
 +    def __init__(self, path):
 +        BzrError.__init__(self)
 +        self.path = path
 +
 +
 +class InvalidSvnBranchPath(NotBranchError):
 +    """Error raised when a path was specified that is not a child of or itself
 +    a valid branch path in the current branching scheme."""
 +    _fmt = """%(path)s is not a valid Subversion branch path in the current 
 +repository layout. See 'bzr help svn-repository-layout' for details."""
 +
 +    def __init__(self, path, layout):
 +        assert isinstance(path, str)
 +        NotBranchError.__init__(self, urllib.quote(path))
 +        self.layout = layout
 +
 +
 +class LayoutUnusable(BzrError):
 +    _fmt = """Unable to use layout %(layout)r with mapping %(mapping)r."""
 +
 +    def __init__(self, layout, mapping):
 +        BzrError.__init__(self)
 +        self.layout = layout
 +        self.mapping = mapping
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..c7dd76cd49971c27d1cf2afd6545789a32f2d3d7
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,258 @@@
++# Copyright (C) 2006,2008 by Jelmer Vernooij
++# 
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 3 of the License, or
++# (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program; if not, write to the Free Software
++# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++"""Upgrading revisions made with older versions of the mapping."""
++
++from bzrlib import ui
++from bzrlib.errors import BzrError, InvalidRevisionId
++from bzrlib.revision import Revision
++from bzrlib.trace import info
++
++import itertools
++from bzrlib.plugins.svn.mapping import mapping_registry
++
++
++class RebaseNotPresent(DependencyNotPresent):
++    _fmt = "Unable to import bzr-rebase (required for upgrade support): %(error)s"
++
++    def __init__(self, error):
++        DependencyNotPresent.__init__(self, 'bzr-rebase', error)
++
++
++def check_rebase_version(min_version):
++    """Check what version of bzr-rebase is installed.
++
++    Raises an exception when the version installed is older than 
++    min_version.
++
++    :raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
++    """
++    try:
++        from bzrlib.plugins.rebase import version_info as rebase_version_info
++        if rebase_version_info[:2] < min_version:
++            raise RebaseNotPresent("Version %r present, at least %r required" 
++                                   % (rebase_version_info, min_version))
++    except ImportError, e:
++        raise RebaseNotPresent(e)
++
++
++
++class UpgradeChangesContent(BzrError):
++    """Inconsistency was found upgrading the mapping of a revision."""
++    _fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
++
++    def __init__(self, revid):
++        self.revid = revid
++
++
++
++def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
++    """Create a new revision id for an upgraded version of a revision.
++    
++    Prevents suffix to be appended needlessly.
++
++    :param revid: Original revision id.
++    :return: New revision id
++    """
++    if revid.endswith(upgrade_suffix):
++        return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
++    else:
++        return revid + mapping_suffix + upgrade_suffix
++
++
++def determine_fileid_renames(old_tree, new_tree):
++    for old_file_id in old_tree:
++        new_file_id = new_tree.path2id(old_tree.id2path(old_file_id))
++        if new_file_id is not None:
++            yield old_file_id, new_file_id
++
++
++def upgrade_workingtree(wt, foreign_repository, new_mapping, mapping_registry, 
++                        allow_changes=False, verbose=False):
++    """Upgrade a working tree.
++
++    :param foreign_repository: Foreign repository object
++    """
++    wt.lock_write()
++    try:
++        old_revid = wt.last_revision()
++        renames = upgrade_branch(wt.branch, foreign_repository, new_mapping=new_mapping,
++                                 mapping_registry=mapping_registry,
++                                 allow_changes=allow_changes, verbose=verbose)
++        last_revid = wt.branch.last_revision()
++        wt.set_last_revision(last_revid)
++
++        # Adjust file ids in working tree
++        for (old_fileid, new_fileid) in determine_fileid_renames(wt.branch.repository.revision_tree(old_revid), wt.basis_tree()):
++            path = wt.id2path(old_fileid)
++            wt.remove(path)
++            wt.add([path], [new_fileid])
++    finally:
++        wt.unlock()
++
++    return renames
++
++
++def upgrade_branch(branch, foreign_repository, new_mapping=None, 
++                   allow_changes=False, verbose=False):
++    """Upgrade a branch to the current mapping version.
++    
++    :param branch: Branch to upgrade.
++    :param foreign_repository: Repository to fetch new revisions from
++    :param allow_changes: Allow changes in mappings.
++    :param verbose: Whether to print verbose list of rewrites
++    """
++    revid = branch.last_revision()
++    renames = upgrade_repository(branch.repository, foreign_repository, 
++              revision_id=revid, new_mapping=new_mapping,
++              allow_changes=allow_changes, verbose=verbose)
++    if len(renames) > 0:
++        branch.generate_revision_history(renames[revid])
++    return renames
++
++
++def check_revision_changed(oldrev, newrev):
++    """Check if two revisions are different. This is exactly the same 
++    as Revision.equals() except that it does not check the revision_id."""
++    if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
++        newrev.timestamp != oldrev.timestamp or
++        newrev.message != oldrev.message or
++        newrev.timezone != oldrev.timezone or
++        newrev.committer != oldrev.committer or
++        newrev.properties != oldrev.properties):
++        raise UpgradeChangesContent(oldrev.revision_id)
++
++
++def generate_upgrade_map(new_mapping, revs, mapping_registry):
++    """Generate an upgrade map for use by bzr-rebase.
++
++    :param new_mapping: Mapping to upgrade revisions to.
++    :param revs: Iterator over revisions to upgrade.
++    :return: Map from old revids as keys, new revids as values stored in a 
++             dictionary.
++    """
++    rename_map = {}
++    # Create a list of revisions that can be renamed during the upgade
++    for revid in revs:
++        assert isinstance(revid, str)
++        try:
++            (foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
++        except InvalidRevisionId:
++            # Not a foreign revision, nothing to do
++            continue
++        newrevid = new_mapping.revision_id_foreign_to_bzr(foreign_revid)
++        if revid == newrevid:
++            continue
++        rename_map[revid] = newrevid
++
++    return rename_map
++
++MIN_REBASE_VERSION = (0, 4)
++
++def create_upgrade_plan(repository, foreign_repository, new_mapping,
++                        mapping_registry, revision_id=None, allow_changes=False):
++    """Generate a rebase plan for upgrading revisions.
++
++    :param repository: Repository to do upgrade in
++    :param foreign_repository: Subversion repository to fetch new revisions from.
++    :param new_mapping: New mapping to use.
++    :param revision_id: Revision to upgrade (None for all revisions in 
++        repository.)
++    :param allow_changes: Whether an upgrade is allowed to change the contents
++        of revisions.
++    :return: Tuple with a rebase plan and map of renamed revisions.
++    """
++    from bzrlib.plugins.rebase.rebase import generate_transpose_plan
++    check_rebase_version(MIN_REBASE_VERSION)
++
++    graph = repository.get_graph()
++    if revision_id is None:
++        potential = repository.all_revision_ids()
++    else:
++        potential = itertools.imap(lambda (rev, parents): rev, 
++                graph.iter_ancestry([revision_id]))
++    upgrade_map = generate_upgrade_map(new_mapping, potential, mapping_registry)
++   
++    # Make sure all the required current version revisions are present
++    for revid in upgrade_map.values():
++        if not repository.has_revision(revid):
++            repository.fetch(foreign_repository, revid)
++
++    if not allow_changes:
++        for oldrevid, newrevid in upgrade_map.items():
++            oldrev = repository.get_revision(oldrevid)
++            newrev = repository.get_revision(newrevid)
++            check_revision_changed(oldrev, newrev)
++
++    if revision_id is None:
++        heads = repository.all_revision_ids() 
++    else:
++        heads = [revision_id]
++
++    plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map, 
++      graph,
++      lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
++    def remove_parents((oldrevid, (newrevid, parents))):
++        return (oldrevid, newrevid)
++    upgrade_map.update(dict(map(remove_parents, plan.items())))
++
++    return (plan, upgrade_map)
++
++ 
++def upgrade_repository(repository, foreign_repository, new_mapping, 
++                       mapping_registry, revision_id=None, allow_changes=False, 
++                       verbose=False):
++    """Upgrade the revisions in repository until the specified stop revision.
++
++    :param repository: Repository in which to upgrade.
++    :param foreign_repository: Repository to fetch new revisions from.
++    :param new_mapping: New mapping.
++    :param revision_id: Revision id up until which to upgrade, or None for 
++                        all revisions.
++    :param allow_changes: Allow changes to mappings.
++    :param verbose: Whether to print list of rewrites
++    :return: Dictionary of mapped revisions
++    """
++    check_rebase_version(MIN_REBASE_VERSION)
++    from bzrlib.plugins.rebase.rebase import (
++        replay_snapshot, rebase, rebase_todo)
++
++    # Find revisions that need to be upgraded, create
++    # dictionary with revision ids in key, new parents in value
++    try:
++        repository.lock_write()
++        foreign_repository.lock_read()
++        (plan, revid_renames) = create_upgrade_plan(repository, foreign_repository, 
++                                                    new_mapping, mapping_registry,
++                                                    revision_id=revision_id,
++                                                    allow_changes=allow_changes)
++        if verbose:
++            for revid in rebase_todo(repository, plan):
++                info("%s -> %s" % (revid, plan[revid][0]))
++        def fix_revid(revid):
++            try:
++                (foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
++            except InvalidRevisionId:
++                return revid
++            return new_mapping.revision_id_foreign_to_bzr(foreign_revid)
++        def replay(repository, oldrevid, newrevid, new_parents):
++            return replay_snapshot(repository, oldrevid, newrevid, new_parents,
++                                   revid_renames, fix_revid)
++        rebase(repository, plan, replay)
++        return revid_renames
++    finally:
++        repository.unlock()
++        foreign_repository.unlock()
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..e6bfe944e3fd3b87e525cd1362d0e18e7a7bdf1a
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,77 @@@
++# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
++ 
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 3 of the License, or
++# (at your option) any later version.
++
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++
++# You should have received a copy of the GNU General Public License
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.
++
++"""Branch tests."""
++
++from bzrlib import urlutils
++from bzrlib.branch import Branch
++from bzrlib.bzrdir import BzrDir
++from bzrlib.errors import NoSuchFile, NoSuchRevision, NotBranchError, NoSuchTag
++from bzrlib.repository import Repository
++from bzrlib.revision import NULL_REVISION
++from bzrlib.trace import mutter
++
++import os
++from unittest import TestCase
++
++from bzrlib.plugins.svn import core
++from bzrlib.plugins.svn.branch import FakeControlFiles, SvnBranchFormat
++from bzrlib.plugins.svn.convert import load_dumpfile
++from bzrlib.plugins.svn.mapping import SVN_PROP_BZR_REVISION_ID
++from bzrlib.plugins.svn.tests import SubversionTestCase
++
++class WorkingSubversionBranch(SubversionTestCase):
++
++    def setUp(self):
++        super(TestSubversionMappingRepositoryWorks, self).setUp()
++        self._old_mapping = mapping_registry._get_default_key()
++        mapping_registry.set_default(self.mapping_name)
++
++    def tearDown(self):
++        super(TestSubversionMappingRepositoryWorks, self).tearDown()
++        mapping_registry.set_default(self._old_mapping)
++
++    def test_revision_id_to_revno_simple(self):
++        repos_url = self.make_repository('a')
++
++        dc = self.get_commit_editor(repos_url)
++        dc.add_file("foo").modify()
++        dc.change_prop("bzr:revision-id:v3-none", 
++                            "2 myrevid\n")
++        dc.close()
++
++        branch = Branch.open(repos_url)
++        self.assertEquals(2, branch.revision_id_to_revno("myrevid"))
++
++    def test_revision_id_to_revno_older(self):
++        repos_url = self.make_repository('a')
++
++        dc = self.get_commit_editor(repos_url)
++        dc.add_file("foo").modify()
++        dc.change_prop("bzr:revision-id:v3-none", 
++                            "2 myrevid\n")
++        dc.close()
++
++        dc = self.get_commit_editor(repos_url)
++        dc.open_file("foo").modify()
++        dc.change_prop("bzr:revision-id:v3-none", 
++                            "2 myrevid\n3 mysecondrevid\n")
++        dc.close()
++
++        branch = Branch.open(repos_url)
++        self.assertEquals(3, branch.revision_id_to_revno("mysecondrevid"))
++        self.assertEquals(2, branch.revision_id_to_revno("myrevid"))
++
++
index d1ee22125a1bcc19ad8d19c42a700a704a10dc8a,0000000000000000000000000000000000000000..1aa3f390cf7eca71d561032b4bafa505e0990f78
mode 100644,000000..100644
--- /dev/null
@@@ -1,340 -1,0 +1,342 @@@
- from bzrlib.plugins.svn.upgrade import (upgrade_repository, upgrade_branch,
 +# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
 +
 +# This program is free software; you can redistribute it and/or modify
 +# it under the terms of the GNU General Public License as published by
 +# the Free Software Foundation; either version 3 of the License, or
 +# (at your option) any later version.
 +
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU General Public License for more details.
 +
 +# You should have received a copy of the GNU General Public License
 +# along with this program; if not, write to the Free Software
 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +
 +"""Mapping upgrade tests."""
 +
 +from bzrlib.bzrdir import BzrDir
 +from bzrlib.repository import Repository
 +from bzrlib.tests import TestCase, TestSkipped
 +
 +from bzrlib.plugins.svn.errors import RebaseNotPresent
 +from bzrlib.plugins.svn.format import get_rich_root_format
 +from bzrlib.plugins.svn.mapping3 import BzrSvnMappingv3FileProps
 +from bzrlib.plugins.svn.mapping3.scheme import TrunkBranchingScheme
 +from bzrlib.plugins.svn.tests import SubversionTestCase
-         upgrade_repository(newrepos, oldrepos, allow_changes=True)
++from bzrlib.plugins.foreign.upgrade import (upgrade_repository, upgrade_branch,
 +                     upgrade_workingtree, UpgradeChangesContent, 
 +                     create_upgraded_revid, generate_upgrade_map)
 +
 +class TestUpgradeChangesContent(TestCase):
 +    def test_init(self):
 +        x = UpgradeChangesContent("revisionx")
 +        self.assertEqual("revisionx", x.revid)
 +
 +
 +class ParserTests(TestCase):
 +    def test_create_upgraded_revid_new(self):
 +        self.assertEqual("bla-svn3-upgrade",
 +                         create_upgraded_revid("bla", "-svn3"))
 +
 +    def test_create_upgraded_revid_upgrade(self):
 +        self.assertEqual("bla-svn3-upgrade",
 +                         create_upgraded_revid("bla-svn1-upgrade", "-svn3"))
 +
 +
 +def skip_no_rebase(unbound):
 +    def check_error(self, *args, **kwargs):
 +        try:
 +            return unbound(self, *args, **kwargs)
 +        except RebaseNotPresent, e:
 +            raise TestSkipped(e)
 +    check_error.__doc__ = unbound.__doc__
 +    check_error.__name__ = unbound.__name__
 +    return check_error
 +
 +
 +class UpgradeTests(SubversionTestCase):
 +    @skip_no_rebase
 +    def test_no_custom(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a","w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +
 +        self.assertTrue(newrepos.has_revision("svn-v1:1@%s-" % oldrepos.uuid))
 +
-         upgrade_repository(newrepos, oldrepos, allow_changes=True)
++        upgrade_repository(newrepos, oldrepos, mapping_registry=mapping_registry, allow_changes=True)
 +
 +        mapping = oldrepos.get_mapping()
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +
 +    @skip_no_rebase
 +    def test_single_custom(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +
-         upgrade_repository(newrepos, oldrepos, allow_changes=True)
++        upgrade_repository(newrepos, oldrepos, mapping_registry=mapping_registry, allow_changes=True)
 +
 +        mapping = oldrepos.get_mapping()
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +        self.assertTrue(newrepos.has_revision("customrev%s-upgrade" % mapping.upgrade_suffix))
 +        newrepos.lock_read()
 +        self.assertTrue((oldrepos.generate_revision_id(1, "", mapping),),
 +                        tuple(newrepos.get_revision("customrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        newrepos.unlock()
 +
 +    @skip_no_rebase
 +    def test_single_keep_parent_fileid(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add(["a"], ["someid"])
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        wt.rename_one("a", "b")
 +        file("f/a", 'w').write("moredata")
 +        wt.add(["a"], ["specificid"])
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +
-         upgrade_repository(newrepos, oldrepos, allow_changes=True)
++        upgrade_repository(newrepos, oldrepos, mapping_registry=mapping_registry, allow_changes=True)
 +        mapping = oldrepos.get_mapping()
 +        tree = newrepos.revision_tree("customrev%s-upgrade" % mapping.upgrade_suffix)
 +        self.assertEqual("specificid", tree.inventory.path2id("a"))
 +        self.assertEqual(mapping.generate_file_id(oldrepos.uuid, 1, "", u"a"), 
 +                         tree.inventory.path2id("b"))
 +
 +    @skip_no_rebase
 +    def test_single_custom_continue(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.add_file("b").modify("c")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        file("f/b", "w").write("c")
 +        wt.add("a")
 +        wt.add("b")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        file("f/b", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +
 +        tree = newrepos.revision_tree("svn-v1:1@%s-" % oldrepos.uuid)
 +
 +        newrepos.lock_write()
 +        newrepos.start_write_group()
 +
 +        mapping = oldrepos.get_mapping()
 +        fileid = tree.inventory.path2id("a")
 +        revid = "customrev%s-upgrade" % mapping.upgrade_suffix
 +        newrepos.texts.add_lines((fileid, revid), 
 +                [(fileid, "svn-v1:1@%s-" % oldrepos.uuid)],
 +                tree.get_file(fileid).readlines())
 +
 +        newrepos.commit_write_group()
 +        newrepos.unlock()
 +
-         renames = upgrade_repository(newrepos, oldrepos, allow_changes=True)
++        upgrade_repository(newrepos, oldrepos, mapping_registry=mapping_registry, 
++                           allow_changes=True)
 +
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +        self.assertTrue(newrepos.has_revision("customrev%s-upgrade" % mapping.upgrade_suffix))
 +        newrepos.lock_read()
 +        self.assertTrue((oldrepos.generate_revision_id(1, "", mapping),),
 +                        tuple(newrepos.get_revision("customrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        newrepos.unlock()
 +
 +    @skip_no_rebase
 +    def test_more_custom(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
 +        mapping = oldrepos.get_mapping()
-         upgrade_branch(b, oldrepos, allow_changes=True)
++        renames = upgrade_repository(newrepos, oldrepos, mapping_registry=mapping_registry, 
++                                     allow_changes=True)
 +        self.assertEqual({
 +            'svn-v1:1@%s-' % oldrepos.uuid: 'svn-v3-none:%s::1' % oldrepos.uuid,
 +            "customrev": "customrev%s-upgrade" % mapping.upgrade_suffix,
 +            "anotherrev": "anotherrev%s-upgrade" % mapping.upgrade_suffix},
 +            renames)
 +
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +        self.assertTrue(newrepos.has_revision("customrev%s-upgrade" % mapping.upgrade_suffix))
 +        self.assertTrue(newrepos.has_revision("anotherrev%s-upgrade" % mapping.upgrade_suffix))
 +        newrepos.lock_read()
 +        self.assertTrue((oldrepos.generate_revision_id(1, "", mapping),),
 +                        tuple(newrepos.get_revision("customrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        self.assertTrue(("customrev-%s-upgrade" % mapping.upgrade_suffix,),
 +                        tuple(newrepos.get_revision("anotherrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        newrepos.unlock()
 +
 +    @skip_no_rebase
 +    def test_more_custom_branch(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
-         upgrade_workingtree(wt, oldrepos, allow_changes=True)
++        upgrade_branch(b, oldrepos, mapping_registry=mapping_registry, allow_changes=True)
 +        mapping = oldrepos.get_mapping()
 +        self.assertEqual([oldrepos.generate_revision_id(0, "", mapping),
 +                          oldrepos.generate_revision_id(1, "", mapping),
 +                          "customrev%s-upgrade" % mapping.upgrade_suffix,
 +                          "anotherrev%s-upgrade" % mapping.upgrade_suffix
 +                          ], b.revision_history())
 +
 +    @skip_no_rebase
 +    def test_workingtree(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
 +        mapping = oldrepos.get_mapping()
-         upgrade_branch(b, oldrepos)
++        upgrade_workingtree(wt, oldrepos, mapping_registry, allow_changes=True)
 +        self.assertEquals(wt.last_revision(), b.last_revision())
 +        self.assertEqual([oldrepos.generate_revision_id(0, "", mapping),
 +                          oldrepos.generate_revision_id(1, "", mapping),
 +                          "customrev%s-upgrade" % mapping.upgrade_suffix,
 +                          "anotherrev%s-upgrade" % mapping.upgrade_suffix
 +                          ], b.revision_history())
 +
 +    @skip_no_rebase
 +    def test_branch_none(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="blarev")
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
-         self.assertRaises(UpgradeChangesContent, upgrade_branch, b, oldrepos)
++        upgrade_branch(b, oldrepos, mapping_registry=mapping_registry)
 +        self.assertEqual(["blarev", "customrev", "anotherrev"],
 +                b.revision_history())
 +
 +    @skip_no_rebase
 +    def test_raise_incompat(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("d").modify("e")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("c")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +
++        self.assertRaises(UpgradeChangesContent, lambda: upgrade_branch(b, oldrepos, mapping_registry=mapping_registry))
 +
 +
 +class TestGenerateUpdateMapTests(TestCase):
 +    def test_nothing(self):
 +        self.assertEquals({}, generate_upgrade_map(BzrSvnMappingv3FileProps(TrunkBranchingScheme()), ["bla", "bloe"]))
 +
 +    def test_v2_to_v3(self):
 +        self.assertEquals({"svn-v2:12@65390229-12b7-0310-b90b-f21a5aa7ec8e-trunk": "svn-v3-trunk0:65390229-12b7-0310-b90b-f21a5aa7ec8e:trunk:12"}, generate_upgrade_map(BzrSvnMappingv3FileProps(TrunkBranchingScheme()), ["svn-v2:12@65390229-12b7-0310-b90b-f21a5aa7ec8e-trunk", "bloe", "blaaa"]))
diff --cc upgrade.py
index 496684473148788f01cb78db846a4a32a137b70a,c7dd76cd49971c27d1cf2afd6545789a32f2d3d7..9ac353d21a8390334f6994c6d54355b39a31d7b0
@@@ -21,270 -21,238 +21,60 @@@ from bzrlib.revision import Revisio
  from bzrlib.trace import info
  
  import itertools
- from bzrlib.plugins.svn import changes, logwalker, mapping, properties
  from bzrlib.plugins.svn.mapping import mapping_registry
  
- class UpgradeChangesContent(BzrError):
-     """Inconsistency was found upgrading the mapping of a revision."""
-     _fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
-     def __init__(self, revid):
-         self.revid = revid
- def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
-     """Create a new revision id for an upgraded version of a revision.
-     
-     Prevents suffix to be appended needlessly.
-     :param revid: Original revision id.
-     :return: New revision id
-     """
-     if revid.endswith(upgrade_suffix):
-         return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
-     else:
-         return revid + mapping_suffix + upgrade_suffix
- def determine_fileid_renames(old_tree, new_tree):
-     for old_file_id in old_tree:
-         new_file_id = new_tree.path2id(old_tree.id2path(old_file_id))
-         if new_file_id is not None:
-             yield old_file_id, new_file_id
- def upgrade_workingtree(wt, svn_repository, new_mapping=None, 
-                         allow_changes=False, verbose=False):
-     """Upgrade a working tree.
-     :param svn_repository: Subversion repository object
-     """
-     wt.lock_write()
-     try:
-         old_revid = wt.last_revision()
-         renames = upgrade_branch(wt.branch, svn_repository, new_mapping=new_mapping,
-                                  allow_changes=allow_changes, verbose=verbose)
-         last_revid = wt.branch.last_revision()
-         wt.set_last_revision(last_revid)
-         # Adjust file ids in working tree
-         for (old_fileid, new_fileid) in determine_fileid_renames(wt.branch.repository.revision_tree(old_revid), wt.basis_tree()):
-             path = wt.id2path(old_fileid)
-             wt.remove(path)
-             wt.add([path], [new_fileid])
-     finally:
-         wt.unlock()
-     return renames
- def upgrade_branch(branch, svn_repository, new_mapping=None, 
-                    allow_changes=False, verbose=False):
-     """Upgrade a branch to the current mapping version.
-     
-     :param branch: Branch to upgrade.
-     :param svn_repository: Repository to fetch new revisions from
-     :param allow_changes: Allow changes in mappings.
-     :param verbose: Whether to print verbose list of rewrites
-     """
-     revid = branch.last_revision()
-     renames = upgrade_repository(branch.repository, svn_repository, 
-               revision_id=revid, new_mapping=new_mapping,
-               allow_changes=allow_changes, verbose=verbose)
-     if len(renames) > 0:
-         branch.generate_revision_history(renames[revid])
-     return renames
- def check_revision_changed(oldrev, newrev):
-     """Check if two revisions are different. This is exactly the same 
-     as Revision.equals() except that it does not check the revision_id."""
-     if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
-         newrev.timestamp != oldrev.timestamp or
-         newrev.message != oldrev.message or
-         newrev.timezone != oldrev.timezone or
-         newrev.committer != oldrev.committer or
-         newrev.properties != oldrev.properties):
-         raise UpgradeChangesContent(oldrev.revision_id)
- def generate_upgrade_map(new_mapping, revs):
-     """Generate an upgrade map for use by bzr-rebase.
-     :param new_mapping: BzrSvnMapping to upgrade revisions to.
-     :param revs: Iterator over revisions to upgrade.
-     :return: Map from old revids as keys, new revids as values stored in a 
-              dictionary.
-     """
-     rename_map = {}
-     # Create a list of revisions that can be renamed during the upgade
-     for revid in revs:
-         assert isinstance(revid, str)
-         try:
-             (uuid, bp, rev, mapping) = mapping_registry.parse_revision_id(revid)
-         except InvalidRevisionId:
-             # Not a bzr-svn revision, nothing to do
-             continue
-         newrevid = new_mapping.revision_id_foreign_to_bzr((uuid, rev, bp))
-         if revid == newrevid:
-             continue
-         rename_map[revid] = newrevid
-     return rename_map
- MIN_REBASE_VERSION = (0, 4)
- def create_upgrade_plan(repository, svn_repository, new_mapping,
-                         revision_id=None, allow_changes=False):
-     """Generate a rebase plan for upgrading revisions.
-     :param repository: Repository to do upgrade in
-     :param svn_repository: Subversion repository to fetch new revisions from.
-     :param new_mapping: New mapping to use.
-     :param revision_id: Revision to upgrade (None for all revisions in 
-         repository.)
-     :param allow_changes: Whether an upgrade is allowed to change the contents
-         of revisions.
-     :return: Tuple with a rebase plan and map of renamed revisions.
-     """
-     from bzrlib.plugins.svn import check_rebase_version
-     from bzrlib.plugins.rebase.rebase import generate_transpose_plan
-     check_rebase_version(MIN_REBASE_VERSION)
-     graph = repository.get_graph()
-     if revision_id is None:
-         potential = repository.all_revision_ids()
-     else:
-         potential = itertools.imap(lambda (rev, parents): rev, 
-                 graph.iter_ancestry([revision_id]))
-     upgrade_map = generate_upgrade_map(new_mapping, potential)
-    
-     # Make sure all the required current version revisions are present
-     for revid in upgrade_map.values():
-         if not repository.has_revision(revid):
-             repository.fetch(svn_repository, revid)
-     if not allow_changes:
-         for oldrevid, newrevid in upgrade_map.items():
-             oldrev = repository.get_revision(oldrevid)
-             newrev = repository.get_revision(newrevid)
-             check_revision_changed(oldrev, newrev)
-     if revision_id is None:
-         heads = repository.all_revision_ids() 
-     else:
-         heads = [revision_id]
-     plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map, 
-       graph,
-       lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
-     def remove_parents((oldrevid, (newrevid, parents))):
-         return (oldrevid, newrevid)
-     upgrade_map.update(dict(map(remove_parents, plan.items())))
-     return (plan, upgrade_map)
-  
- def upgrade_repository(repository, svn_repository, new_mapping=None,
-                        revision_id=None, allow_changes=False, verbose=False):
-     """Upgrade the revisions in repository until the specified stop revision.
-     :param repository: Repository in which to upgrade.
-     :param svn_repository: Repository to fetch new revisions from.
-     :param new_mapping: New mapping.
-     :param revision_id: Revision id up until which to upgrade, or None for 
-                         all revisions.
-     :param allow_changes: Allow changes to mappings.
-     :param verbose: Whether to print list of rewrites
-     :return: Dictionary of mapped revisions
-     """
-     from bzrlib.plugins.svn import check_rebase_version
-     check_rebase_version(MIN_REBASE_VERSION)
-     from bzrlib.plugins.rebase.rebase import (
-         replay_snapshot, rebase, rebase_todo)
-     if new_mapping is None:
-         new_mapping = svn_repository.get_mapping()
-     # Find revisions that need to be upgraded, create
-     # dictionary with revision ids in key, new parents in value
-     try:
-         repository.lock_write()
-         svn_repository.lock_read()
-         (plan, revid_renames) = create_upgrade_plan(repository, svn_repository, 
-                                                     new_mapping,
-                                                     revision_id=revision_id,
-                                                     allow_changes=allow_changes)
-         if verbose:
-             for revid in rebase_todo(repository, plan):
-                 info("%s -> %s" % (revid, plan[revid][0]))
-         def fix_revid(revid):
-             try:
-                 (uuid, bp, rev, mapping) = mapping_registry.parse_revision_id(revid)
-             except InvalidRevisionId:
-                 return revid
-             return new_mapping.revision_id_foreign_to_bzr((uuid, rev, bp))
-         def replay(repository, oldrevid, newrevid, new_parents):
-             return replay_snapshot(repository, oldrevid, newrevid, new_parents,
-                                    revid_renames, fix_revid)
-         rebase(repository, plan, replay)
-         return revid_renames
-     finally:
-         repository.unlock()
-         svn_repository.unlock()
  
 -class RebaseNotPresent(DependencyNotPresent):
 -    _fmt = "Unable to import bzr-rebase (required for upgrade support): %(error)s"
 +def set_revprops(repository, new_mapping, from_revnum=0, to_revnum=None):
 +    """Set bzr-svn revision properties for existing bzr-svn revisions.
  
 -    def __init__(self, error):
 -        DependencyNotPresent.__init__(self, 'bzr-rebase', error)
 -
 -
 -def check_rebase_version(min_version):
 -    """Check what version of bzr-rebase is installed.
 -
 -    Raises an exception when the version installed is older than 
 -    min_version.
 -
 -    :raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
 -    """
 -    try:
 -        from bzrlib.plugins.rebase import version_info as rebase_version_info
 -        if rebase_version_info[:2] < min_version:
 -            raise RebaseNotPresent("Version %r present, at least %r required" 
 -                                   % (rebase_version_info, min_version))
 -    except ImportError, e:
 -        raise RebaseNotPresent(e)
 -
 -
 -
 -class UpgradeChangesContent(BzrError):
 -    """Inconsistency was found upgrading the mapping of a revision."""
 -    _fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
 -
 -    def __init__(self, revid):
 -        self.revid = revid
 -
 -
 -
 -def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
 -    """Create a new revision id for an upgraded version of a revision.
 -    
 -    Prevents suffix to be appended needlessly.
 -
 -    :param revid: Original revision id.
 -    :return: New revision id
 -    """
 -    if revid.endswith(upgrade_suffix):
 -        return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
 -    else:
 -        return revid + mapping_suffix + upgrade_suffix
 -
 -
 -def determine_fileid_renames(old_tree, new_tree):
 -    for old_file_id in old_tree:
 -        new_file_id = new_tree.path2id(old_tree.id2path(old_file_id))
 -        if new_file_id is not None:
 -            yield old_file_id, new_file_id
 -
 -
 -def upgrade_workingtree(wt, foreign_repository, new_mapping, mapping_registry, 
 -                        allow_changes=False, verbose=False):
 -    """Upgrade a working tree.
 -
 -    :param foreign_repository: Foreign repository object
 -    """
 -    wt.lock_write()
 -    try:
 -        old_revid = wt.last_revision()
 -        renames = upgrade_branch(wt.branch, foreign_repository, new_mapping=new_mapping,
 -                                 mapping_registry=mapping_registry,
 -                                 allow_changes=allow_changes, verbose=verbose)
 -        last_revid = wt.branch.last_revision()
 -        wt.set_last_revision(last_revid)
 -
 -        # Adjust file ids in working tree
 -        for (old_fileid, new_fileid) in determine_fileid_renames(wt.branch.repository.revision_tree(old_revid), wt.basis_tree()):
 -            path = wt.id2path(old_fileid)
 -            wt.remove(path)
 -            wt.add([path], [new_fileid])
 -    finally:
 -        wt.unlock()
 -
 -    return renames
 -
 -
 -def upgrade_branch(branch, foreign_repository, new_mapping=None, 
 -                   allow_changes=False, verbose=False):
 -    """Upgrade a branch to the current mapping version.
 -    
 -    :param branch: Branch to upgrade.
 -    :param foreign_repository: Repository to fetch new revisions from
 -    :param allow_changes: Allow changes in mappings.
 -    :param verbose: Whether to print verbose list of rewrites
 -    """
 -    revid = branch.last_revision()
 -    renames = upgrade_repository(branch.repository, foreign_repository, 
 -              revision_id=revid, new_mapping=new_mapping,
 -              allow_changes=allow_changes, verbose=verbose)
 -    if len(renames) > 0:
 -        branch.generate_revision_history(renames[revid])
 -    return renames
 -
 -
 -def check_revision_changed(oldrev, newrev):
 -    """Check if two revisions are different. This is exactly the same 
 -    as Revision.equals() except that it does not check the revision_id."""
 -    if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
 -        newrev.timestamp != oldrev.timestamp or
 -        newrev.message != oldrev.message or
 -        newrev.timezone != oldrev.timezone or
 -        newrev.committer != oldrev.committer or
 -        newrev.properties != oldrev.properties):
 -        raise UpgradeChangesContent(oldrev.revision_id)
 -
 -
 -def generate_upgrade_map(new_mapping, revs, mapping_registry):
 -    """Generate an upgrade map for use by bzr-rebase.
 -
 -    :param new_mapping: Mapping to upgrade revisions to.
 -    :param revs: Iterator over revisions to upgrade.
 -    :return: Map from old revids as keys, new revids as values stored in a 
 -             dictionary.
 -    """
 -    rename_map = {}
 -    # Create a list of revisions that can be renamed during the upgade
 -    for revid in revs:
 -        assert isinstance(revid, str)
 -        try:
 -            (foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
 -        except InvalidRevisionId:
 -            # Not a foreign revision, nothing to do
 -            continue
 -        newrevid = new_mapping.revision_id_foreign_to_bzr(foreign_revid)
 -        if revid == newrevid:
 -            continue
 -        rename_map[revid] = newrevid
 -
 -    return rename_map
 -
 -MIN_REBASE_VERSION = (0, 4)
 -
 -def create_upgrade_plan(repository, foreign_repository, new_mapping,
 -                        mapping_registry, revision_id=None, allow_changes=False):
 -    """Generate a rebase plan for upgrading revisions.
 -
 -    :param repository: Repository to do upgrade in
 -    :param foreign_repository: Subversion repository to fetch new revisions from.
 -    :param new_mapping: New mapping to use.
 -    :param revision_id: Revision to upgrade (None for all revisions in 
 -        repository.)
 -    :param allow_changes: Whether an upgrade is allowed to change the contents
 -        of revisions.
 -    :return: Tuple with a rebase plan and map of renamed revisions.
 +    :param repository: Subversion Repository object.
 +    :param new_mapping: Mapping to upgrade to
      """
 -    from bzrlib.plugins.rebase.rebase import generate_transpose_plan
 -    check_rebase_version(MIN_REBASE_VERSION)
 -
++    from bzrlib.plugins.svn import changes, logwalker, mapping, properties
 +    if to_revnum is None:
 +        to_revnum = repository.get_latest_revnum()
      graph = repository.get_graph()
 -    if revision_id is None:
 -        potential = repository.all_revision_ids()
 -    else:
 -        potential = itertools.imap(lambda (rev, parents): rev, 
 -                graph.iter_ancestry([revision_id]))
 -    upgrade_map = generate_upgrade_map(new_mapping, potential, mapping_registry)
 -   
 -    # Make sure all the required current version revisions are present
 -    for revid in upgrade_map.values():
 -        if not repository.has_revision(revid):
 -            repository.fetch(foreign_repository, revid)
 -
 -    if not allow_changes:
 -        for oldrevid, newrevid in upgrade_map.items():
 -            oldrev = repository.get_revision(oldrevid)
 -            newrev = repository.get_revision(newrevid)
 -            check_revision_changed(oldrev, newrev)
 -
 -    if revision_id is None:
 -        heads = repository.all_revision_ids() 
 -    else:
 -        heads = [revision_id]
 -
 -    plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map, 
 -      graph,
 -      lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
 -    def remove_parents((oldrevid, (newrevid, parents))):
 -        return (oldrevid, newrevid)
 -    upgrade_map.update(dict(map(remove_parents, plan.items())))
 -
 -    return (plan, upgrade_map)
 -
 - 
 -def upgrade_repository(repository, foreign_repository, new_mapping, 
 -                       mapping_registry, revision_id=None, allow_changes=False, 
 -                       verbose=False):
 -    """Upgrade the revisions in repository until the specified stop revision.
 -
 -    :param repository: Repository in which to upgrade.
 -    :param foreign_repository: Repository to fetch new revisions from.
 -    :param new_mapping: New mapping.
 -    :param revision_id: Revision id up until which to upgrade, or None for 
 -                        all revisions.
 -    :param allow_changes: Allow changes to mappings.
 -    :param verbose: Whether to print list of rewrites
 -    :return: Dictionary of mapped revisions
 -    """
 -    check_rebase_version(MIN_REBASE_VERSION)
 -    from bzrlib.plugins.rebase.rebase import (
 -        replay_snapshot, rebase, rebase_todo)
 -
 -    # Find revisions that need to be upgraded, create
 -    # dictionary with revision ids in key, new parents in value
 +    assert from_revnum <= to_revnum
 +    pb = ui.ui_factory.nested_progress_bar()
 +    logcache = getattr(repository._log, "cache", None)
      try:
 -        repository.lock_write()
 -        foreign_repository.lock_read()
 -        (plan, revid_renames) = create_upgrade_plan(repository, foreign_repository, 
 -                                                    new_mapping, mapping_registry,
 -                                                    revision_id=revision_id,
 -                                                    allow_changes=allow_changes)
 -        if verbose:
 -            for revid in rebase_todo(repository, plan):
 -                info("%s -> %s" % (revid, plan[revid][0]))
 -        def fix_revid(revid):
 -            try:
 -                (foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
 -            except InvalidRevisionId:
 -                return revid
 -            return new_mapping.revision_id_foreign_to_bzr(foreign_revid)
 -        def replay(repository, oldrevid, newrevid, new_parents):
 -            return replay_snapshot(repository, oldrevid, newrevid, new_parents,
 -                                   revid_renames, fix_revid)
 -        rebase(repository, plan, replay)
 -        return revid_renames
 +        for (paths, revnum, revprops) in repository._log.iter_changes(None, to_revnum, from_revnum, pb=pb):
 +            if revnum == 0:
 +                # Never a bzr-svn revision
 +                continue
 +            # Find the root path of the change
 +            bp = changes.changes_root(paths.keys())
 +            if bp is None:
 +                fileprops = {}
 +            else:
 +                fileprops = logwalker.lazy_dict({}, repository.branchprop_list.get_properties, bp, revnum)
 +            old_mapping = mapping.find_mapping(revprops, fileprops)
 +            if old_mapping is None:
 +                # Not a bzr-svn revision
 +                if not mapping.SVN_REVPROP_BZR_SKIP in revprops:
 +                    repository.transport.change_rev_prop(revnum, mapping.SVN_REVPROP_BZR_SKIP, "")
 +                continue
 +            if old_mapping == new_mapping:
 +                # Already the latest mapping
 +                continue
 +            assert old_mapping.can_use_revprops or bp is not None
 +            new_revprops = dict(revprops.items())
 +            revmeta = repository._revmeta_provider.get_revision(bp, revnum, changes, revprops, fileprops)
 +            rev = revmeta.get_revision(old_mapping)
 +            revno = graph.find_distance_to_null(rev.revision_id, [])
 +            assert bp is not None
 +            new_mapping.export_revision(bp, rev.timestamp, rev.timezone, rev.committer, rev.properties, rev.revision_id, revno, rev.parent_ids, new_revprops, None)
 +            new_mapping.export_fileid_map(old_mapping.import_fileid_map(revprops, fileprops), 
 +                new_revprops, None)
 +            new_mapping.export_text_parents(old_mapping.import_text_parents(revprops, fileprops), new_revprops, None)
 +            new_mapping.export_text_revisions(old_mapping.import_text_revisions(revprops, fileprops), new_revprops, None)
 +            if rev.message != mapping.parse_svn_log(revprops.get(properties.PROP_REVISION_LOG)):
 +                new_mapping.export_message(rev.message, new_revprops, None)
 +            changed_revprops = dict(filter(lambda (k,v): k not in revprops or revprops[k] != v, new_revprops.items()))
 +            if logcache is not None:
 +                logcache.drop_revprops(revnum)
 +            for k, v in changed_revprops.items():
 +                repository.transport.change_rev_prop(revnum, k, v)
 +            # Might as well update the cache while we're at it
      finally:
 -        repository.unlock()
 -        foreign_repository.unlock()
 -
 +        pb.finished()