Use bzr-foreign VcsMapping function names for converting between bzr and svn revids.
authorJelmer Vernooij <jelmer@samba.org>
Fri, 29 Aug 2008 16:00:52 +0000 (18:00 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Fri, 29 Aug 2008 16:00:52 +0000 (18:00 +0200)
1  2 
foreign/TODO
foreign/__init__.py
log.py
mapping.py
mapping3/__init__.py
mapping4.py
revids.py
tests/test_branch.py
tests/test_fileids.py
tests/test_mapping.py
upgrade.py

diff --cc foreign/TODO
index ea6b55eac64ad621989ee1b185ae2e43d26787f8,0000000000000000000000000000000000000000..2d3f16c7c5827fdc87f9a197d1397b7ece0a9767
mode 100644,000000..100644
--- /dev/null
@@@ -1,3 -1,0 +1,1 @@@
- - Import VirtualRevisionTexts, VirtualInventoryTexts, VirtualSignatureTexts
- - Import dpush command
 +- Import CommitBuilder-based fetcher
index b349fc81c5fcfefbf7451a50c19acb24454b0be5,0000000000000000000000000000000000000000..22ac8f6631408231f7fe45c9455406bd0aa8859d
mode 100644,000000..100644
--- /dev/null
@@@ -1,133 -1,0 +1,149 @@@
 +# Copyright (C) 2008 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 2 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
 +
 +"""Foreign branch utilities."""
 +
 +from bzrlib import errors, registry
 +from bzrlib.commands import Command, Option
 +
 +
 +class VcsMapping(object):
 +    """Describes the mapping between the semantics of Bazaar and a foreign vcs.
 +
 +    """
 +    experimental = False
 +    roundtripping = False
 +    revid_prefix = None
 +
++    def revision_id_bzr_to_foreign(self, bzr_revid):
++        """Parse a bzr revision id and convert it to a foreign revid.
++
++        :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)
++
++    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)
++
 +
 +class VcsMappingRegistry(registry.Registry):
 +    """Registry for Bazaar<->foreign VCS mappings.
 +    
 +    There should be one instance of this registry for every foreign VCS.
 +    """
 +
 +    def register(self, key, factory, help):
 +        """Register a mapping between Bazaar and foreign VCS semantics.
 +
 +        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)
 +
 +    def set_default(self, key):
 +        """Set the 'default' key to be a clone of the supplied key.
 +
 +        This method must be called once and only once.
 +        """
 +        self._set_default_key(key)
 +
 +    def get_default(self):
 +        """Convenience function for obtaining the default mapping to use."""
 +        return self.get(self._get_default_key())
 +
 +
 +class FakeControlFiles(object):
 +    """Dummy implementation of ControlFiles.
 +    
 +    This is required as some code relies on controlfiles being 
 +    available."""
 +    def get_utf8(self, name):
 +        raise errors.NoSuchFile(name)
 +
 +    def get(self, name):
 +        raise errors.NoSuchFile(name)
 +
 +    def break_lock(self):
 +        pass
 +
 +
 +class cmd_dpush(Command):
 +    """Push diffs into a foreign version control system without any 
 +    Bazaar-specific metadata.
 +
 +    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. 
 +    """
 +    takes_args = ['location?']
 +    takes_options = ['remember', Option('directory',
 +            help='Branch to push from, '
 +                 'rather than the one containing the working directory.',
 +            short_name='d',
 +            type=unicode,
 +            ),
 +            Option('no-rebase', help="Don't rebase after push")]
 +
 +    def run(self, location=None, remember=False, directory=None, 
 +            no_rebase=False):
 +        from bzrlib import urlutils
 +        from bzrlib.bzrdir import BzrDir
 +        from bzrlib.branch import Branch
 +        from bzrlib.errors import BzrCommandError, NoWorkingTree
 +        from bzrlib.workingtree import WorkingTree
 +
 +        if directory is None:
 +            directory = "."
 +        try:
 +            source_wt = WorkingTree.open_containing(directory)[0]
 +            source_branch = source_wt.branch
 +        except NoWorkingTree:
 +            source_branch = Branch.open_containing(directory)[0]
 +            source_wt = None
 +        stored_loc = source_branch.get_push_location()
 +        if location is None:
 +            if stored_loc is None:
 +                raise BzrCommandError("No push location known or specified.")
 +            else:
 +                display_url = urlutils.unescape_for_display(stored_loc,
 +                        self.outf.encoding)
 +                self.outf.write("Using saved location: %s\n" % display_url)
 +                location = stored_loc
 +
 +        bzrdir = BzrDir.open(location)
 +        target_branch = bzrdir.open_branch()
 +        target_branch.lock_write()
 +        revid_map = source_branch.dpush(target_branch)
 +        # We successfully created the target, remember it
 +        if source_branch.get_push_location() is None or remember:
 +            source_branch.set_push_location(target_branch.base)
 +        if not no_rebase:
 +            _, old_last_revid = source_branch.last_revision_info()
 +            new_last_revid = revid_map[old_last_revid]
 +            if source_wt is not None:
 +                source_wt.pull(target_branch, overwrite=True, 
 +                               stop_revision=new_last_revid)
 +            else:
 +                source_branch.pull(target_branch, overwrite=True, 
 +                                   stop_revision=new_last_revid)
 +
 +
diff --cc log.py
index 33e2b1b0f5c3510b78120cead4c1d5053a176869,0000000000000000000000000000000000000000..4f4da15877c3cba016143e93ed63a0643ba15654
mode 100644,000000..100644
--- 1/log.py
--- /dev/null
+++ b/log.py
@@@ -1,37 -1,0 +1,37 @@@
-             (uuid, bp, revnum, mapp) = mapping.parse_revision_id(rev.revision_id)
 +# 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/>.
 +
 +from bzrlib.errors import InvalidRevisionId
 +
 +from bzrlib.plugins.svn import mapping
 +
 +def show_subversion_properties(rev):
 +    data = None
 +    ret = {}
 +    if getattr(rev, "svn_meta", None) is not None:
 +        data = (rev.svn_meta.revnum, rev.svn_meta.branch_path)
 +    else:
 +        try:
++            (uuid, bp, revnum, mapp) = mapping.revision_id_bzr_to_foreign(rev.revision_id)
 +        except InvalidRevisionId:
 +            pass
 +        else:
 +            data = (revnum, bp)
 +
 +    if data is not None:
 +        return { "svn revno": "%d (on /%s)" % data}
 +    return {}
 +
 +
diff --cc mapping.py
index fe29570dab29f2a9de5a90ee14ac9e4f934d4ca8,0000000000000000000000000000000000000000..67ac5a8d9247e263b98532f28bd6eebd301004d4
mode 100644,000000..100644
--- /dev/null
@@@ -1,706 -1,0 +1,706 @@@
-     def parse_revision_id(self, revid):
 +# Copyright (C) 2005-2008 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/>.
 +
 +"""Maps between Subversion and Bazaar semantics."""
 +
 +from bzrlib import osutils, registry
 +from bzrlib.errors import InvalidRevisionId
 +from bzrlib.trace import mutter
 +
 +from bzrlib.plugins.svn import errors, foreign, properties, version_info
 +import calendar
 +import time
 +import urllib
 +
 +SVN_PROP_BZR_PREFIX = 'bzr:'
 +SVN_PROP_BZR_ANCESTRY = 'bzr:ancestry:'
 +SVN_PROP_BZR_FILEIDS = 'bzr:file-ids'
 +SVN_PROP_BZR_MERGE = 'bzr:merge'
 +SVN_PROP_BZR_REVISION_INFO = 'bzr:revision-info'
 +SVN_PROP_BZR_REVISION_ID = 'bzr:revision-id:'
 +SVN_PROP_BZR_TEXT_PARENTS = 'bzr:text-parents'
 +SVN_PROP_BZR_LOG = 'bzr:log'
 +
 +SVN_REVPROP_BZR_COMMITTER = 'bzr:committer'
 +SVN_REVPROP_BZR_FILEIDS = 'bzr:file-ids'
 +SVN_REVPROP_BZR_MAPPING_VERSION = 'bzr:mapping-version'
 +SVN_REVPROP_BZR_MERGE = 'bzr:merge'
 +SVN_REVPROP_BZR_REVISION_ID = 'bzr:revision-id'
 +SVN_REVPROP_BZR_REVNO = 'bzr:revno'
 +SVN_REVPROP_BZR_REVPROP_PREFIX = 'bzr:revprop:'
 +SVN_REVPROP_BZR_ROOT = 'bzr:root'
 +SVN_REVPROP_BZR_SIGNATURE = 'bzr:gpg-signature'
 +SVN_REVPROP_BZR_TIMESTAMP = 'bzr:timestamp'
 +SVN_REVPROP_BZR_LOG = 'bzr:log'
 +SVN_REVPROP_BZR_TEXT_PARENTS = 'bzr:text-parents'
 +SVN_REVPROP_BZR_REQUIRED_FEATURES = 'bzr:required-features'
 +
 +
 +def escape_svn_path(x):
 +    """Escape a Subversion path for use in a revision identifier.
 +
 +    :param x: Path
 +    :return: Escaped path
 +    """
 +    assert isinstance(x, str)
 +    return urllib.quote(x, "")
 +unescape_svn_path = urllib.unquote
 +
 +
 +# The following two functions don't use day names (which can vary by 
 +# locale) unlike the alternatives in bzrlib.timestamp
 +
 +def format_highres_date(t, offset=0):
 +    """Format a date, such that it includes higher precision in the
 +    seconds field.
 +
 +    :param t:   The local time in fractional seconds since the epoch
 +    :type t: float
 +    :param offset:  The timezone offset in integer seconds
 +    :type offset: int
 +    """
 +    assert isinstance(t, float)
 +
 +    # This has to be formatted for "original" date, so that the
 +    # revision XML entry will be reproduced faithfully.
 +    if offset is None:
 +        offset = 0
 +    tt = time.gmtime(t + offset)
 +
 +    return (time.strftime("%Y-%m-%d %H:%M:%S", tt)
 +            # Get the high-res seconds, but ignore the 0
 +            + ('%.9f' % (t - int(t)))[1:]
 +            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 +
 +
 +def unpack_highres_date(date):
 +    """This takes the high-resolution date stamp, and
 +    converts it back into the tuple (timestamp, timezone)
 +    Where timestamp is in real UTC since epoch seconds, and timezone is an
 +    integer number of seconds offset.
 +
 +    :param date: A date formated by format_highres_date
 +    :type date: string
 +    """
 +    # skip day if applicable
 +    if not date[0].isdigit():
 +        space_loc = date.find(' ')
 +        if space_loc == -1:
 +            raise ValueError("No valid date: %r" % date)
 +        date = date[space_loc+1:]
 +    # Up until the first period is a datestamp that is generated
 +    # as normal from time.strftime, so use time.strptime to
 +    # parse it
 +    dot_loc = date.find('.')
 +    if dot_loc == -1:
 +        raise ValueError(
 +            'Date string does not contain high-precision seconds: %r' % date)
 +    base_time = time.strptime(date[:dot_loc], "%Y-%m-%d %H:%M:%S")
 +    fract_seconds, offset = date[dot_loc:].split()
 +    fract_seconds = float(fract_seconds)
 +
 +    offset = int(offset)
 +
 +    hours = int(offset / 100)
 +    minutes = (offset % 100)
 +    seconds_offset = (hours * 3600) + (minutes * 60)
 +
 +    # time.mktime returns localtime, but calendar.timegm returns UTC time
 +    timestamp = calendar.timegm(base_time)
 +    timestamp -= seconds_offset
 +    # Add back in the fractional seconds
 +    timestamp += fract_seconds
 +    return (timestamp, seconds_offset)
 +
 +
 +def parse_merge_property(line):
 +    """Parse a bzr:merge property value.
 +
 +    :param line: Line to parse
 +    :return: List of revisions merged
 +    """
 +    if ' ' in line:
 +        mutter('invalid revision id %r in merged property, skipping', line)
 +        return ()
 +
 +    return tuple(filter(lambda x: x != "", line.split("\t")))
 +
 +
 +def parse_svn_dateprop(date):
 +    return (properties.time_from_cstring(date) / 1000000.0, 0)
 +
 +
 +def parse_svn_revprops(svn_revprops, rev):
 +    if svn_revprops.has_key(properties.PROP_REVISION_AUTHOR):
 +        rev.committer = svn_revprops[properties.PROP_REVISION_AUTHOR]
 +    else:
 +        rev.committer = ""
 +    
 +    rev.message = svn_revprops.get(properties.PROP_REVISION_LOG)
 +
 +    if rev.message:
 +        try:
 +            rev.message = rev.message.decode("utf-8")
 +        except UnicodeDecodeError:
 +            pass
 +
 +    assert svn_revprops.has_key(properties.PROP_REVISION_DATE)
 +    (rev.timestamp, rev.timezone) = parse_svn_dateprop(svn_revprops[properties.PROP_REVISION_DATE])
 +    rev.properties = {}
 +
 +
 +def parse_revision_metadata(text, rev):
 +    """Parse a revision info text (as set in bzr:revision-info).
 +
 +    :param text: text to parse
 +    :param rev: Revision object to apply read parameters to
 +    """
 +    in_properties = False
 +    for l in text.splitlines():
 +        try:
 +            key, value = l.split(": ", 2)
 +        except ValueError:
 +            raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO, 
 +                    "Missing : in revision metadata")
 +        if key == "committer":
 +            rev.committer = value.decode("utf-8")
 +        elif key == "timestamp":
 +            (rev.timestamp, rev.timezone) = unpack_highres_date(value)
 +        elif key == "properties":
 +            in_properties = True
 +        elif key[0] == "\t" and in_properties:
 +            rev.properties[str(key[1:])] = value.decode("utf-8")
 +        else:
 +            raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_INFO, 
 +                    "Invalid key %r" % key)
 +
 +
 +def parse_revid_property(line):
 +    """Parse a (revnum, revid) tuple as set in revision id properties.
 +    :param line: line to parse
 +    :return: tuple with (bzr_revno, revid)
 +    """
 +    if '\n' in line:
 +        raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID, 
 +                "newline in revision id property line")
 +    try:
 +        (revno, revid) = line.split(' ', 1)
 +    except ValueError:
 +        raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID, 
 +                "missing space")
 +    if revid == "":
 +        raise errors.InvalidPropertyValue(SVN_PROP_BZR_REVISION_ID,
 +                "empty revision id")
 +    return (int(revno), revid)
 +
 +
 +def generate_revision_metadata(timestamp, timezone, committer, revprops):
 +    """Generate revision metadata text for the specified revision 
 +    properties.
 +
 +    :param timestamp: timestamp of the revision, in seconds since epoch
 +    :param timezone: timezone, specified by offset from GMT in seconds
 +    :param committer: name/email of the committer
 +    :param revprops: dictionary with custom revision properties
 +    :return: text with data to set bzr:revision-info to.
 +    """
 +    assert timestamp is None or isinstance(timestamp, float)
 +    text = ""
 +    if timestamp is not None:
 +        text += "timestamp: %s\n" % format_highres_date(timestamp, timezone) 
 +    if committer is not None:
 +        text += "committer: %s\n" % committer.encode("utf-8")
 +    if revprops is not None and revprops != {}:
 +        text += "properties: \n"
 +        for k, v in sorted(revprops.items()):
 +            text += "\t%s: %s\n" % (k.encode("utf-8"), v.encode("utf-8"))
 +    assert isinstance(text, str)
 +    return text
 +
 +
 +def parse_bzr_svn_revprops(props, rev):
 +    """Update a Revision object from a set of Subversion revision properties.
 +    
 +    :param props: Dictionary with Subversion revision properties.
 +    :param rev: Revision object
 +    """
 +    if props.has_key(SVN_REVPROP_BZR_TIMESTAMP):
 +        (rev.timestamp, rev.timezone) = unpack_highres_date(props[SVN_REVPROP_BZR_TIMESTAMP])
 +
 +    if props.has_key(SVN_REVPROP_BZR_COMMITTER):
 +        rev.committer = props[SVN_REVPROP_BZR_COMMITTER].decode("utf-8")
 +
 +    if props.has_key(SVN_REVPROP_BZR_LOG):
 +        rev.message = props[SVN_REVPROP_BZR_LOG]
 +
 +    for name, value in props.items():
 +        if name.startswith(SVN_REVPROP_BZR_REVPROP_PREFIX):
 +            rev.properties[name[len(SVN_REVPROP_BZR_REVPROP_PREFIX):]] = value
 +
 +
 +class BzrSvnMapping(foreign.VcsMapping):
 +    """Class that maps between Subversion and Bazaar semantics."""
 +    experimental = False
 +    _warned_experimental = False
 +
 +    def __init__(self):
 +        if (version_info[3] == 'exp' or self.experimental) and not BzrSvnMapping._warned_experimental:
 +            from bzrlib.trace import warning
 +            warning("using experimental bzr-svn mappings; may break existing branches in the most horrible ways")
 +            BzrSvnMapping._warned_experimental = True
 +
 +    @classmethod
 +    def from_repository(cls, repository, _hinted_branch_path=None):
 +        return cls()
 +
 +    @classmethod
 +    def supports_roundtripping(cls):
 +        """Whether this mapping supports roundtripping.
 +        """
 +        return False
 +
 +    @classmethod
 +    def supports_custom_revprops(cls):
 +        """Whether this mapping can be used with custom revision properties."""
 +        return False
 +
 +    def is_bzr_revision(self, revprops, fileprops):
 +        """Whether this is a revision that was pushed by Bazaar."""
 +        return False
 +
 +    @classmethod
 +    def supports_custom_fileprops(cls):
 +        """Whether this mapping can be used with custom file properties."""
 +        return False
 +
 +    def get_mandated_layout(self, repository):
 +        """Return the repository layout if any is mandated by this mapping, 
 +        None otherwise."""
 +        return None
 +
-         raise NotImplementedError(self.parse_revision_id)
++    def revision_id_bzr_to_foreign(self, revid):
 +        """Parse an existing Subversion-based revision id.
 +
 +        :param revid: The revision id.
 +        :raises: InvalidRevisionId
 +        :return: Tuple with uuid, branch path, revision number and scheme.
 +        """
-     def generate_revision_id(self, (uuid, revnum, path)):
++        raise NotImplementedError(self.revision_id_bzr_to_foreign)
 +
-         raise NotImplementedError(self.generate_revision_id)
++    def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
 +        """Generate a unambiguous revision id. 
 +        
 +        :param uuid: UUID of the repository.
 +        :param revnum: Subversion revision number.
 +        :param path: Branch path.
 +
 +        :return: New revision id.
 +        """
-     def parse_revision_id(cls, revid):
++        raise NotImplementedError(self.revision_id_foreign_to_bzr)
 +
 +    def is_branch(self, branch_path):
 +        raise NotImplementedError(self.is_branch)
 +
 +    def is_tag(self, tag_path):
 +        raise NotImplementedError(self.is_tag)
 +
 +    @staticmethod
 +    def generate_file_id(uuid, revnum, branch, inv_path):
 +        """Create a file id identifying a Subversion file.
 +
 +        :param uuid: UUID of the repository
 +        :param revnum: Revision number at which the file was introduced.
 +        :param branch: Branch path of the branch in which the file was introduced.
 +        :param inv_path: Original path of the file within the inventory
 +        """
 +        raise NotImplementedError(self.generate_file_id)
 +
 +    def import_revision(self, revprops, fileprops, uuid, branch, revnum, rev):
 +        """Update a Revision object from Subversion revision and branch 
 +        properties.
 +
 +        :param revprops: Dictionary with Subversion revision properties.
 +        :param fileprops: Dictionary with Subversion file properties on the 
 +                          branch root.
 +        :param revnum: Revision number in Subversion.
 +        :param rev: Revision object to import data into.
 +        """
 +        raise NotImplementedError(self.import_revision)
 +
 +    def get_rhs_parents(self, branch_path, revprops, fileprops):
 +        """Obtain the right-hand side parents for a revision.
 +
 +        """
 +        raise NotImplementedError(self.get_rhs_parents)
 +
 +    def get_rhs_ancestors(self, branch_path, revprops, fileprops):
 +        """Obtain the right-hand side ancestors for a revision.
 +
 +        """
 +        raise NotImplementedError(self.get_rhs_ancestors)
 +
 +    def import_fileid_map(self, revprops, fileprops):
 +        """Obtain the file id map for a revision from the properties.
 +
 +        """
 +        raise NotImplementedError(self.import_fileid_map)
 +
 +    def export_fileid_map(self, can_use_custom_revprops, fileids, revprops, fileprops):
 +        """Adjust the properties for a file id map.
 +
 +        :param fileids: Dictionary
 +        :param revprops: Subversion revision properties
 +        :param fileprops: File properties
 +        """
 +        raise NotImplementedError(self.export_fileid_map)
 +
 +    def import_text_parents(self, revprops, fileprops):
 +        """Obtain a text parent map from properties.
 +
 +        :param revprops: Subversion revision properties.
 +        :param fileprops: File properties.
 +        """
 +        raise NotImplementedError(self.import_text_parents)
 +
 +    def export_text_parents(self, can_use_custom_revprops, text_parents, revprops, fileprops):
 +        """Store a text parent map.
 +
 +        :param text_parents: Text parent map
 +        :param revprops: Revision properties
 +        :param fileprops: File properties
 +        """
 +        raise NotImplementedError(self.export_text_parents)
 +
 +    def export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, fileprops):
 +        """Determines the revision properties and branch root file 
 +        properties.
 +        """
 +        raise NotImplementedError(self.export_revision)
 +
 +    def export_message(self, can_use_custom_revprops, log, revprops, fileprops):
 +        raise NotImplementedError(self.export_message)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        raise NotImplementedError(self.get_revision_id)
 +
 +    def unprefix(self, branch_path, repos_path):
 +        raise NotImplementedError(self.unprefix)
 +
 +
 +class BzrSvnMappingv1(BzrSvnMapping):
 +    """This was the initial version of the mappings as used by bzr-svn
 +    0.2.
 +    
 +    It does not support pushing revisions to Subversion as-is, but only 
 +    as part of a merge.
 +    """
 +    @classmethod
-     def generate_revision_id(self, (uuid, revnum, path)):
++    def revision_id_bzr_to_foreign(cls, revid):
 +        if not revid.startswith("svn-v1:"):
 +            raise InvalidRevisionId(revid, "")
 +        revid = revid[len("svn-v1:"):]
 +        at = revid.index("@")
 +        fash = revid.rindex("-")
 +        uuid = revid[at+1:fash]
 +        branch_path = unescape_svn_path(revid[fash+1:])
 +        revnum = int(revid[0:at])
 +        assert revnum >= 0
 +        return (uuid, branch_path, revnum, cls())
 +
-     def parse_revision_id(cls, revid):
++    def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
 +        return "svn-v1:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other)
 +
 +
 +class BzrSvnMappingv2(BzrSvnMapping):
 +    """The second version of the mappings as used in the 0.3.x series.
 +
 +    """
 +    @classmethod
-     def generate_revision_id(self, (uuid, revnum, path)):
++    def revision_id_bzr_to_foreign(cls, revid):
 +        if not revid.startswith("svn-v2:"):
 +            raise InvalidRevisionId(revid, "")
 +        revid = revid[len("svn-v2:"):]
 +        at = revid.index("@")
 +        fash = revid.rindex("-")
 +        uuid = revid[at+1:fash]
 +        branch_path = unescape_svn_path(revid[fash+1:])
 +        revnum = int(revid[0:at])
 +        assert revnum >= 0
 +        return (uuid, branch_path, revnum, cls())
 +
-     return mapping.parse_revision_id(revid)
++    def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
 +        return "svn-v2:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other)
 +
 +
 +def parse_fileid_property(text):
 +    """Pares a fileid file or revision property.
 +
 +    :param text: Property value
 +    :return: Map of path -> fileid
 +    """
 +    ret = {}
 +    for line in text.splitlines():
 +        (path, key) = line.split("\t", 1)
 +        ret[urllib.unquote(path)] = osutils.safe_file_id(key)
 +    return ret
 +
 +
 +def generate_fileid_property(fileids):
 +    """Marshall a dictionary with file ids.
 +    
 +    :param fileids: Map of path -> fileid
 +    :return: Property value
 +    """
 +    return "".join(["%s\t%s\n" % (urllib.quote(path.encode("utf-8")), fileids[path]) for path in sorted(fileids.keys())])
 +
 +
 +def parse_text_parents_property(text):
 +    ret = {}
 +    for line in text.splitlines():
 +        (entry, parent_revid) = line.split("\t", 1)
 +        ret[urllib.unquote(entry)] = osutils.safe_revision_id(parent_revid)
 +    return ret
 +
 +
 +def generate_text_parents_property(text_parents):
 +    return "".join(["%s\t%s\n" % (urllib.quote(path.encode("utf-8")), text_parents[path]) for path in sorted(text_parents.keys())])
 +
 +
 +class BzrSvnMappingFileProps(object):
 +    def __init__(self, name):
 +        self.name = name
 +
 +    @classmethod
 +    def supports_custom_fileprops(cls):
 +        """Whether this mapping can be used with custom file properties."""
 +        return True
 +
 +    def import_revision(self, svn_revprops, fileprops, uuid, branch, revnum, rev):
 +        parse_svn_revprops(svn_revprops, rev)
 +        if SVN_PROP_BZR_LOG in fileprops:
 +            rev.message = fileprops[SVN_PROP_BZR_LOG]
 +        metadata = fileprops.get(SVN_PROP_BZR_REVISION_INFO)
 +        if metadata is not None:
 +            parse_revision_metadata(metadata, rev)
 +
 +    def import_text_parents(self, svn_revprops, fileprops):
 +        metadata = fileprops.get(SVN_PROP_BZR_TEXT_PARENTS)
 +        if metadata is None:
 +            return {}
 +        return parse_text_parents_property(metadata)
 +
 +    def export_text_parents(self, can_use_custom_revprops, text_parents, svn_revprops, fileprops):
 +        if text_parents != {}:
 +            fileprops[SVN_PROP_BZR_TEXT_PARENTS] = generate_text_parents_property(text_parents)
 +        else:
 +            fileprops[SVN_PROP_BZR_TEXT_PARENTS] = ""
 +
 +    def get_rhs_parents(self, branch_path, revprops, fileprops):
 +        bzr_merges = fileprops.get(SVN_PROP_BZR_ANCESTRY+self.name, None)
 +        if bzr_merges is not None:
 +            return parse_merge_property(bzr_merges.splitlines()[-1])
 +
 +        return ()
 +
 +    def get_rhs_ancestors(self, branch_path, revprops, fileprops):
 +        ancestry = []
 +        for l in fileprops.get(SVN_PROP_BZR_ANCESTRY+self.name, "").splitlines():
 +            ancestry.extend(l.split("\n"))
 +        return ancestry
 +
 +    def import_fileid_map(self, svn_revprops, fileprops):
 +        fileids = fileprops.get(SVN_PROP_BZR_FILEIDS, None)
 +        if fileids is None:
 +            return {}
 +        return parse_fileid_property(fileids)
 +
 +    def record_merges(self, merges, fileprops):
 +        """Store the extra merges (non-LHS parents) in a file property.
 +
 +        :param merges: List of parents.
 +        """
 +        # Bazaar Parents
 +        old = fileprops.get(SVN_PROP_BZR_ANCESTRY+self.name, "")
 +        svnprops = { SVN_PROP_BZR_ANCESTRY+self.name: old + "\t".join(merges) + "\n" }
 +
 +        return svnprops
 + 
 +    def export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, old_fileprops):
 +
 +        # Keep track of what Subversion properties to set later on
 +        fileprops = {}
 +        fileprops[SVN_PROP_BZR_REVISION_INFO] = generate_revision_metadata(
 +            timestamp, timezone, committer, revprops)
 +
 +        if len(merges) > 0:
 +            fileprops.update(self.record_merges(merges, old_fileprops))
 +
 +        # Set appropriate property if revision id was specified by 
 +        # caller
 +        if revision_id is not None:
 +            old = old_fileprops.get(SVN_PROP_BZR_REVISION_ID+self.name, "")
 +            fileprops[SVN_PROP_BZR_REVISION_ID+self.name] = old + "%d %s\n" % (revno, revision_id)
 +
 +        return ({}, fileprops)
 +
 +    def export_message(self, can_use_custom_revprops, message, revprops, fileprops):
 +        fileprops[SVN_PROP_BZR_LOG] = message.encode("utf-8")
 +
 +    def is_bzr_revision(self, revprops, fileprops):
 +        return fileprops.has_key(SVN_PROP_BZR_REVISION_ID+self.name)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        # Lookup the revision from the bzr:revision-id-vX property
 +        text = fileprops.get(SVN_PROP_BZR_REVISION_ID+self.name, None)
 +        if text is None:
 +            return (None, None)
 +
 +        lines = text.splitlines()
 +        if len(lines) == 0:
 +            return (None, None)
 +
 +        try:
 +            return parse_revid_property(lines[-1])
 +        except errors.InvalidPropertyValue, e:
 +            mutter(str(e))
 +            return (None, None)
 +
 +    def export_fileid_map(self, can_use_custom_revprops, fileids, revprops, fileprops):
 +        if fileids != {}:
 +            file_id_text = generate_fileid_property(fileids)
 +            fileprops[SVN_PROP_BZR_FILEIDS] = file_id_text
 +        else:
 +            fileprops[SVN_PROP_BZR_FILEIDS] = ""
 +
 +
 +class BzrSvnMappingRevProps(object):
 +    @classmethod
 +    def supports_custom_revprops(cls):
 +        """Whether this mapping can be used with custom revision properties."""
 +        return True
 +
 +    def import_revision(self, svn_revprops, fileprops, uuid, branch, revnum, rev):
 +        parse_svn_revprops(svn_revprops, rev)
 +        parse_bzr_svn_revprops(svn_revprops, rev)
 +
 +    def import_fileid_map(self, svn_revprops, fileprops):
 +        if not svn_revprops.has_key(SVN_REVPROP_BZR_FILEIDS):
 +            return {}
 +        return parse_fileid_property(svn_revprops[SVN_REVPROP_BZR_FILEIDS])
 +
 +    def import_text_parents(self, svn_revprops, fileprops):
 +        if not svn_revprops.has_key(SVN_REVPROP_BZR_TEXT_PARENTS):
 +            return {}
 +        return parse_text_parents_property(svn_revprops[SVN_REVPROP_BZR_TEXT_PARENTS])
 +
 +    def export_text_parents(self, can_use_custom_revprops, text_parents, svn_revprops, fileprops):
 +        svn_revprops[SVN_REVPROP_BZR_TEXT_PARENTS] = generate_text_parents_property(text_parents)
 +
 +    def get_rhs_parents(self, branch_path, svn_revprops, 
 +                        fileprops):
 +        if svn_revprops[SVN_REVPROP_BZR_ROOT] != branch:
 +            return []
 +        return svn_revprops.get(SVN_REVPROP_BZR_MERGE, "").splitlines()
 +
 +    def is_bzr_revision(self, revprops, fileprops):
 +        return revprops.has_key(SVN_REVPROP_BZR_MAPPING_VERSION)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        if not self.is_bzr_revision(revprops, fileprops):
 +            return (None, None)
 +        if revprops[SVN_REVPROP_BZR_ROOT] == branch_path:
 +            revid = revprops[SVN_REVPROP_BZR_REVISION_ID]
 +            revno = int(revprops[SVN_REVPROP_BZR_REVNO])
 +            return (revno, revid)
 +        return (None, None)
 +
 +    def export_message(self, can_use_custom_revprops, message, revprops, fileprops):
 +        revprops[SVN_REVPROP_BZR_LOG] = message.encode("utf-8")
 +
 +    def export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, fileprops):
 +        svn_revprops = {}
 +
 +        if timestamp is not None:
 +            svn_revprops[SVN_REVPROP_BZR_TIMESTAMP] = format_highres_date(timestamp, timezone)
 +
 +        if committer is not None:
 +            svn_revprops[SVN_REVPROP_BZR_COMMITTER] = committer.encode("utf-8")
 +
 +        if revprops is not None:
 +            for name, value in revprops.items():
 +                svn_revprops[SVN_REVPROP_BZR_REVPROP_PREFIX+name] = value.encode("utf-8")
 +
 +        svn_revprops[SVN_REVPROP_BZR_ROOT] = branch_root
 +
 +        if revision_id is not None:
 +            svn_revprops[SVN_REVPROP_BZR_REVISION_ID] = revision_id
 +
 +        if merges != []:
 +            svn_revprops[SVN_REVPROP_BZR_MERGE] = "".join([x+"\n" for x in merges])
 +        svn_revprops[SVN_REVPROP_BZR_REVNO] = str(revno)
 +
 +        return (svn_revprops, {})
 +
 +    def export_fileid_map(self, can_use_custom_revprops, fileids, revprops, fileprops):
 +        revprops[SVN_REVPROP_BZR_FILEIDS] = generate_fileid_property(fileids)
 +
 +    def get_rhs_ancestors(self, branch_path, revprops, fileprops):
 +        raise NotImplementedError(self.get_rhs_ancestors)
 +
 +
 +mapping_registry = foreign.VcsMappingRegistry()
 +mapping_registry.register('v1', BzrSvnMappingv1,
 +        'Original bzr-svn mapping format')
 +mapping_registry.register('v2', BzrSvnMappingv2,
 +        'Second format')
 +mapping_registry.register_lazy('v3', 'bzrlib.plugins.svn.mapping3', 
 +                               'BzrSvnMappingv3FileProps', 
 +                               'Default third format')
 +mapping_registry.register_lazy('v4', 'bzrlib.plugins.svn.mapping4', 
 +                               'BzrSvnMappingv4',
 +                               'Fourth format')
 +mapping_registry.set_default('v3')
 +
 +def parse_mapping_name(name):
 +    assert isinstance(name, str)
 +    if "-" in name:
 +        name, rest = name.split("-", 1)
 +        assert isinstance(rest, str)
 +        return mapping_registry.get(name)(rest)
 +    return mapping_registry.get(name)()
 +
 +
 +def parse_revision_id(revid):
 +    """Try to parse a Subversion revision id.
 +    
 +    :param revid: Revision id to parse
 +    :return: tuple with (uuid, branch_path, revno, mapping)
 +    """
 +    if not revid.startswith("svn-"):
 +        raise InvalidRevisionId(revid, None)
 +    mapping_version = revid[len("svn-"):len("svn-vx")]
 +    mapping = mapping_registry.get(mapping_version)
++    return mapping.revision_id_bzr_to_foreign(revid)
 +
 +def get_default_mapping():
 +    return mapping_registry.get_default()
 +
 +
index b197079e4f6bc8dfc5291d428190ef03c4729065,0000000000000000000000000000000000000000..1cb2c41612e86f56371ce7426a591419b40cb4bf
mode 100644,000000..100644
--- /dev/null
@@@ -1,380 -1,0 +1,380 @@@
-     def parse_revision_id(cls, revid):
 +# Copyright (C) 2005-2008 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/>.
 +
 +from bzrlib import osutils, ui
 +from bzrlib.errors import InvalidRevisionId
 +from bzrlib.trace import mutter
 +
 +from bzrlib.plugins.svn import mapping, properties
 +from bzrlib.plugins.svn.core import SubversionException, NODE_DIR
 +from bzrlib.plugins.svn.errors import ERR_FS_NOT_DIRECTORY, ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND
 +from bzrlib.plugins.svn.layout import RepositoryLayout
 +from bzrlib.plugins.svn.mapping3.scheme import (BranchingScheme, guess_scheme_from_branch_path, 
 +                             guess_scheme_from_history, ListBranchingScheme, 
 +                             parse_list_scheme_text, NoBranchingScheme,
 +                             TrunkBranchingScheme, ListBranchingScheme)
 +from bzrlib.plugins.svn.ra import DIRENT_KIND
 +import sha
 +
 +SVN_PROP_BZR_BRANCHING_SCHEME = 'bzr:branching-scheme'
 +
 +# Number of revisions to evaluate when guessing the branching scheme
 +SCHEME_GUESS_SAMPLE_SIZE = 2000
 +
 +def expand_branch_pattern(begin, todo, check_path, get_children, project=None):
 +    """Find the paths in the repository that match the expected branch pattern.
 +
 +    :param begin: List of path elements currently opened.
 +    :param todo: List of path elements to still evaluate (including wildcards)
 +    :param check_path: Function for checking a path exists
 +    :param get_children: Function for retrieving the children of a path
 +    """
 +    mutter('expand branches: %r, %r', begin, todo)
 +    path = "/".join(begin)
 +    if (project is not None and 
 +        not project.startswith(path) and 
 +        not path.startswith(project)):
 +        return []
 +    # If all elements have already been handled, just check the path exists
 +    if len(todo) == 0:
 +        if check_path(path):
 +            return [path]
 +        else:
 +            return []
 +    # Not a wildcard? Just expand next bits
 +    if todo[0] != "*":
 +        return expand_branch_pattern(begin+[todo[0]], todo[1:], check_path, 
 +                                     get_children, project)
 +    children = get_children(path)
 +    if children is None:
 +        return []
 +    ret = []
 +    pb = ui.ui_factory.nested_progress_bar()
 +    try:
 +        for idx, c in enumerate(children):
 +            pb.update("browsing branches", idx, len(children))
 +            if len(todo) == 1:
 +                # Last path element, so return directly
 +                ret.append("/".join(begin+[c]))
 +            else:
 +                ret += expand_branch_pattern(begin+[c], todo[1:], check_path, 
 +                                             get_children, project)
 +    finally:
 +        pb.finished()
 +    return ret
 +
 +
 +class SchemeDerivedLayout(RepositoryLayout):
 +    def __init__(self, repository, scheme):
 +        self.repository = repository
 +        self.scheme = scheme
 +
 +    def parse(self, path):
 +        (proj, bp, rp) = self.scheme.unprefix(path)
 +        if self.scheme.is_tag(bp):
 +            type = "tag"
 +        else:
 +            type = "branch"
 +        return (type, proj, bp, rp)
 +
 +    def get_tag_name(self, path, project=None):
 +        return path.split("/")[-1]
 +
 +    def _get_root_paths(self, itemlist, revnum, verify_fn, project=None, pb=None):
 +        def check_path(path):
 +            return self.repository.transport.check_path(path, revnum) == NODE_DIR
 +        def find_children(path):
 +            try:
 +                assert not path.startswith("/")
 +                dirents = self.repository.transport.get_dir(path, revnum, DIRENT_KIND)[0]
 +            except SubversionException, (msg, num):
 +                if num in (ERR_FS_NOT_DIRECTORY, ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND):
 +                    return None
 +                raise
 +            return [d for d in dirents if dirents[d]['kind'] == NODE_DIR]
 +
 +        for idx, pattern in enumerate(itemlist):
 +            if pb is not None:
 +                pb.update("finding branches", idx, len(itemlist))
 +            for bp in expand_branch_pattern([], pattern.split("/"), check_path,
 +                    find_children, project):
 +                if verify_fn(bp, project):
 +                    yield "", bp, bp.split("/")[-1]
 +
 +    def get_branches(self, revnum, project=None, pb=None):
 +        return self._get_root_paths(self.scheme.branch_list, revnum, self.scheme.is_branch, project, pb)
 +
 +    def get_tags(self, revnum, project=None, pb=None):
 +        return self._get_root_paths(self.scheme.tag_list, revnum, self.scheme.is_tag, project, pb)
 +
 +    def get_tag_path(self, name, project=""):
 +        return self.scheme.get_tag_path(name, project)
 +
 +    def get_branch_path(self, name, project=""):
 +        return self.scheme.get_branch_path(name, project)
 +
 +    def get_branch_path(self, name, project=""):
 +        return self.scheme.get_branch_path(name, project)
 +
 +    def is_branch_parent(self, path, project=None):
 +        # Na, na, na...
 +        return self.scheme.is_branch_parent(path, project)
 +
 +    def is_tag_parent(self, path, project=None):
 +        # Na, na, na...
 +        return self.scheme.is_tag_parent(path, project)
 +
 +    def push_merged_revisions(self, project=""):
 +        try:
 +            self.scheme.get_branch_path("somebranch")
 +            return self.repository.get_config().get_push_merged_revisions()
 +        except NotImplementedError:
 +            return False
 +
 +    def __repr__(self):
 +        return "%s(%s)" % (self.__class__.__name__, repr(self.scheme))
 +
 +
 +def get_stored_scheme(repository):
 +    """Retrieve the stored branching scheme, either in the repository 
 +    or in the configuration file.
 +    """
 +    scheme = repository.get_config().get_branching_scheme()
 +    guessed_scheme = repository.get_config().get_guessed_branching_scheme()
 +    is_mandatory = repository.get_config().branching_scheme_is_mandatory()
 +    if scheme is not None:
 +        return (scheme, guessed_scheme, is_mandatory)
 +
 +    last_revnum = repository.get_latest_revnum()
 +    scheme = get_property_scheme(repository, last_revnum)
 +    if scheme is not None:
 +        return (scheme, None, True)
 +
 +    return (None, guessed_scheme, False)
 +
 +
 +def get_property_scheme(repository, revnum=None):
 +    if revnum is None:
 +        revnum = repository.get_latest_revnum()
 +    text = repository.branchprop_list.get_properties("", revnum).get(SVN_PROP_BZR_BRANCHING_SCHEME, None)
 +    if text is None:
 +        return None
 +    return ListBranchingScheme(parse_list_scheme_text(text))
 +
 +
 +def set_property_scheme(repository, scheme):
 +    conn = repository.transport.get_connection()
 +    try:
 +        editor = conn.get_commit_editor(
 +            {properties.PROP_REVISION_LOG: "Updating branching scheme for Bazaar."},
 +            None, None, False)
 +        root = editor.open_root()
 +        root.change_prop(SVN_PROP_BZR_BRANCHING_SCHEME, 
 +                "".join(map(lambda x: x+"\n", scheme.branch_list)).encode("utf-8"))
 +        root.close()
 +        editor.close()
 +    finally:
 +        repository.transport.add_connection(conn)
 +
 +
 +def repository_guess_scheme(repository, last_revnum, branch_path=None):
 +    pb = ui.ui_factory.nested_progress_bar()
 +    try:
 +        (guessed_scheme, scheme) = guess_scheme_from_history(
 +            repository._log.iter_changes(None, last_revnum, max(0, last_revnum-SCHEME_GUESS_SAMPLE_SIZE), pb=pb), last_revnum, branch_path)
 +    finally:
 +        pb.finished()
 +    mutter("Guessed branching scheme: %r, guess scheme to use: %r" % 
 +            (guessed_scheme, scheme))
 +    return (guessed_scheme, scheme)
 +
 +
 +def config_set_scheme(repository, scheme, guessed_scheme, mandatory=False):
 +    if guessed_scheme is None:
 +        guessed_scheme_str = None
 +    else:
 +        guessed_scheme_str = str(guessed_scheme)
 +    repository.get_config().set_branching_scheme(str(scheme), 
 +                            guessed_scheme_str, mandatory=mandatory)
 +
 +def set_branching_scheme(repository, scheme, mandatory=False):
 +    repository.get_mapping().scheme = scheme
 +    config_set_scheme(repository, scheme, scheme, mandatory)
 +
 +
 +class BzrSvnMappingv3(mapping.BzrSvnMapping):
 +    """The third version of the mappings as used in the 0.4.x series.
 +
 +    Relies exclusively on file properties, though 
 +    bzr-svn 0.4.11 and up will set some revision properties 
 +    as well if possible.
 +    """
 +    experimental = False
 +    upgrade_suffix = "-svn3"
 +    revid_prefix = "svn-v3-"
 +
 +    def __init__(self, scheme, guessed_scheme=None):
 +        mapping.BzrSvnMapping.__init__(self)
 +        if isinstance(scheme, str):
 +            self.scheme = BranchingScheme.find_scheme(scheme)
 +        else:
 +            self.scheme = scheme
 +        self.guessed_scheme = guessed_scheme
 +
 +    def get_mandated_layout(self, repository):
 +        return SchemeDerivedLayout(repository, self.scheme)
 +
 +    def get_guessed_layout(self, repository):
 +        return SchemeDerivedLayout(repository, self.guessed_scheme or self.scheme)
 +
 +    @classmethod
 +    def from_repository(cls, repository, _hinted_branch_path=None):
 +        (actual_scheme, guessed_scheme, mandatory) = get_stored_scheme(repository)
 +        if mandatory:
 +            return cls(actual_scheme, guessed_scheme) 
 +
 +        if actual_scheme is not None:
 +            if (_hinted_branch_path is None or 
 +                actual_scheme.is_branch(_hinted_branch_path)):
 +                return cls(actual_scheme, guessed_scheme)
 +
 +        last_revnum = repository.get_latest_revnum()
 +        (guessed_scheme, actual_scheme) = repository_guess_scheme(repository, last_revnum, _hinted_branch_path)
 +        if last_revnum > 20:
 +            config_set_scheme(repository, actual_scheme, guessed_scheme, mandatory=False)
 +
 +        return cls(actual_scheme, guessed_scheme)
 +
 +    def __repr__(self):
 +        return "%s(%r)" % (self.__class__.__name__, self.scheme)
 +
 +    def generate_file_id(self, uuid, revnum, branch, inv_path):
 +        assert isinstance(uuid, str)
 +        assert isinstance(revnum, int)
 +        assert isinstance(branch, str)
 +        assert isinstance(inv_path, unicode)
 +        inv_path = inv_path.encode("utf-8")
 +        ret = "%d@%s:%s:%s" % (revnum, uuid, mapping.escape_svn_path(branch), 
 +                               mapping.escape_svn_path(inv_path))
 +        if len(ret) > 150:
 +            ret = "%d@%s:%s;%s" % (revnum, uuid, 
 +                                mapping.escape_svn_path(branch),
 +                                sha.new(inv_path).hexdigest())
 +        assert isinstance(ret, str)
 +        return osutils.safe_file_id(ret)
 +
 +    @staticmethod
 +    def supports_roundtripping():
 +        return True
 +
 +    @classmethod
 +    def _parse_revision_id(cls, revid):
 +        assert isinstance(revid, str)
 +
 +        if not revid.startswith(cls.revid_prefix):
 +            raise InvalidRevisionId(revid, "")
 +
 +        try:
 +            (version, uuid, branch_path, srevnum) = revid.split(":")
 +        except ValueError:
 +            raise InvalidRevisionId(revid, "")
 +
 +        scheme = version[len(cls.revid_prefix):]
 +
 +        branch_path = mapping.unescape_svn_path(branch_path)
 +
 +        return (uuid, branch_path, int(srevnum), scheme)
 +
 +    @classmethod
-     def generate_revision_id(self, (uuid, revnum, path)):
++    def revision_id_bzr_to_foreign(cls, revid):
 +        (uuid, branch_path, srevnum, scheme) = cls._parse_revision_id(revid)
 +        # Some older versions of bzr-svn 0.4 did not always set a branching
 +        # scheme but set "undefined" instead.
 +        if scheme == "undefined":
 +            scheme = guess_scheme_from_branch_path(branch_path)
 +        else:
 +            scheme = BranchingScheme.find_scheme(scheme)
 +
 +        return (uuid, branch_path, srevnum, cls(scheme))
 +
 +    def is_branch(self, branch_path):
 +        return self.scheme.is_branch(branch_path)
 +
 +    def is_tag(self, tag_path):
 +        return self.scheme.is_tag(tag_path)
 +
 +    @classmethod
 +    def _generate_revision_id(cls, uuid, revnum, path, scheme):
 +        assert isinstance(revnum, int)
 +        assert isinstance(path, str)
 +        assert revnum >= 0
 +        assert revnum > 0 or path == "", \
 +                "Trying to generate revid for (%r,%r)" % (path, revnum)
 +        return "%s%s:%s:%s:%d" % (cls.revid_prefix, scheme, uuid, \
 +                       mapping.escape_svn_path(path.strip("/")), revnum)
 +
++    def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
 +        return self._generate_revision_id(uuid, revnum, path, self.scheme)
 +
 +    def unprefix(self, branch_path, repos_path):
 +        (proj, bp, np) = self.scheme.unprefix(repos_path)
 +        assert branch_path == bp
 +        return np
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other) and self.scheme == other.scheme
 +
 +    def __str__(self):
 +        return "%s(%s)" % (self.__class__.__name__, repr(self.scheme))
 +
 +
 +class BzrSvnMappingv3FileProps(mapping.BzrSvnMappingFileProps, BzrSvnMappingv3):
 +
 +    def __init__(self, scheme, guessed_scheme=None):
 +        BzrSvnMappingv3.__init__(self, scheme, guessed_scheme)
 +        self.name = "v3-" + str(scheme)
 +        self.revprop_map = mapping.BzrSvnMappingRevProps()
 +
 +    def export_text_parents(self, can_use_custom_revprops, text_parents, svn_revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_text_parents(self, can_use_custom_revprops, text_parents, svn_revprops, fileprops)
 +        if can_use_custom_revprops:
 +            self.revprop_map.export_text_parents(can_use_custom_revprops, text_parents, svn_revprops, fileprops)
 +
 +    def export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, old_fileprops):
 +        (svn_revprops, fileprops) = mapping.BzrSvnMappingFileProps.export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, old_fileprops)
 +        if can_use_custom_revprops:
 +            (extra_svn_revprops, _) = self.revprop_map.export_revision(can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, None, revno, merges, old_fileprops)
 +            svn_revprops.update(extra_svn_revprops)
 +        return (svn_revprops, fileprops)
 +
 +    def export_fileid_map(self, can_use_custom_revprops, fileids, revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_fileid_map(self, can_use_custom_revprops, fileids, revprops, fileprops)
 +        if can_use_custom_revprops:
 +            self.revprop_map.export_fileid_map(can_use_custom_revprops, fileids, revprops, fileprops)
 +
 +    def export_message(self, can_use_custom_revprops, log, revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_message(self, can_use_custom_revprops, log, revprops, fileprops)
 +        if can_use_custom_revprops:
 +            self.revprop_map.export_message(can_use_custom_revprops, log, revprops, fileprops)
 +
 +
 +class BzrSvnMappingv3RevProps(mapping.BzrSvnMappingRevProps, BzrSvnMappingv3):
 +    def export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, fileprops):
 +        (revprops, fileprops) = mapping.BzrSvnMappingRevProps.export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, merges, fileprops)
 +        revprops[mapping.SVN_REVPROP_BZR_MAPPING_VERSION] = "3"
 +        return (revprops, fileprops)
 +
 +
 +
diff --cc mapping4.py
index 5886b45009217655b083a9afbe783ce3aa986872,0000000000000000000000000000000000000000..d9d9ccb9c73c46aee989ce041eb054487d71f325
mode 100644,000000..100644
--- /dev/null
@@@ -1,130 -1,0 +1,130 @@@
-     def parse_revision_id(cls, revid):
 +# 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/>.
 +
 +from bzrlib import errors
 +
 +from bzrlib.plugins.svn import mapping
 +
 +supported_features = set()
 +
 +
 +class BzrSvnMappingv4(mapping.BzrSvnMapping):
 +    """Mapping between Subversion and Bazaar, introduced in bzr-svn 0.5.
 +
 +    Tries to use revision properties when possible.
 +
 +    TODO: Add variable with required features.
 +    """
 +    revid_prefix = "svn-v4"
 +    upgrade_suffix = "-svn4"
 +    experimental = True
 +
 +    def __init__(self):
 +        self.name = "v4"
 +        self.revprops = mapping.BzrSvnMappingRevProps()
 +        self.fileprops = mapping.BzrSvnMappingFileProps(self.name)
 +
 +    @staticmethod
 +    def supports_roundtripping():
 +        return True
 +
 +    @classmethod
-     def generate_revision_id(self, (uuid, revnum, path)):
++    def revision_id_bzr_to_foreign(cls, revid):
 +        assert isinstance(revid, str)
 +
 +        if not revid.startswith(cls.revid_prefix):
 +            raise errors.InvalidRevisionId(revid, "")
 +
 +        try:
 +            (version, uuid, branch_path, srevnum) = revid.split(":")
 +        except ValueError:
 +            raise errors.InvalidRevisionId(revid, "")
 +
 +        branch_path = mapping.unescape_svn_path(branch_path)
 +
 +        return (uuid, branch_path, int(srevnum), cls())
 +
++    def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
 +        return "svn-v4:%s:%s:%d" % (uuid, path, revnum)
 +
 +    def generate_file_id(self, uuid, revnum, branch, inv_path):
 +        return "%d@%s:%s/%s" % (revnum, uuid, branch, inv_path.encode("utf-8"))
 +
 +    def is_branch(self, branch_path):
 +        return True
 +
 +    def is_tag(self, tag_path):
 +        return True
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other)
 +
 +    def get_rhs_parents(self, branch_path, svn_revprops, fileprops):
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return self.revprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        if revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return self.revprops.get_revision_id(branch_path, revprops, fileprops)
 +        else:
 +            return self.fileprops.get_revision_id(branch_path, revprops, fileprops)
 +
 +    def import_text_parents(self, svn_revprops, fileprops):
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_TEXT_PARENTS):
 +            return self.revprops.import_text_parents(svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.import_text_parents(svn_revprops, fileprops)
 +
 +    def import_fileid_map(self, svn_revprops, fileprops):
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
 +            return self.revprops.import_fileid_map(svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.import_fileid_map(svn_revprops, fileprops)
 +
 +    def export_revision(self, can_use_custom_revprops, branch_root, timestamp, timezone, committer, revprops, revision_id, 
 +                        revno, merges, fileprops):
 +        if can_use_custom_revprops:
 +            (svn_revprops, fileprops) = self.revprops.export_revision(can_use_custom_revprops, branch_root, timestamp, timezone, committer, 
 +                                          revprops, revision_id, revno, merges, fileprops)
 +            svn_revprops[mapping.SVN_REVPROP_BZR_MAPPING_VERSION] = "4"
 +            return (svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.export_revision(can_use_custom_revprops, branch_root, timestamp, timezone, committer, 
 +                                      revprops, revision_id, revno, merges, fileprops)
 +
 +    def export_fileid_map(self, can_use_custom_revprops, fileids, revprops, fileprops):
 +        if can_use_custom_revprops:
 +            self.revprops.export_fileid_map(can_use_custom_revprops, fileids, revprops, fileprops)
 +        else:
 +            self.fileprops.export_fileid_map(can_use_custom_revprops, fileids, revprops, fileprops)
 +
 +    def export_text_parents(self, can_use_custom_revprops, text_parents, revprops, fileprops):
 +        if can_use_custom_revprops:
 +            self.revprops.export_text_parents(can_use_custom_revprops, text_parents, revprops, fileprops)
 +        else:
 +            self.fileprops.export_text_parents(can_use_custom_revprops, text_parents, revprops, fileprops)
 +
 +    def import_revision(self, svn_revprops, fileprops, uuid, branch, revnum, rev):
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_REQUIRED_FEATURES):
 +            features = set(svn_revprops[mapping.SVN_REVPROP_BZR_REQUIRED_FEATURES].split(","))
 +            assert features.issubset(supported_features), "missing feature: %r" % features.difference(supported_features)
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
 +            self.revprops.import_revision(svn_revprops, fileprops, uuid, branch, revnum, rev)
 +        else:
 +            self.fileprops.import_revision(svn_revprops, fileprops, uuid, branch, revnum, rev)
 +
 +
diff --cc revids.py
index fd1bfc9c1e3509c340933df22edfee118fd70bdc,0000000000000000000000000000000000000000..18269e862e713c1f38fa7d4f1552c9488e092443
mode 100644,000000..100644
--- /dev/null
+++ b/revids.py
@@@ -1,315 -1,0 +1,315 @@@
-             return mapping.generate_revision_id((self.repos.uuid, revnum, path))
 +# Copyright (C) 2006-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
 +
 +"""Revision id generation and caching."""
 +
 +from bzrlib.errors import (InvalidRevisionId, NoSuchRevision)
 +from bzrlib.trace import mutter
 +
 +from bzrlib.plugins.svn.cache import CacheTable
 +from bzrlib.plugins.svn.core import SubversionException
 +from bzrlib.plugins.svn.errors import InvalidPropertyValue, ERR_FS_NO_SUCH_REVISION, InvalidBzrSvnRevision
 +from bzrlib.plugins.svn.mapping import (parse_revision_id, BzrSvnMapping, 
 +                     SVN_PROP_BZR_REVISION_ID, parse_revid_property,
 +                     parse_mapping_name)
 +
 +class RevidMap(object):
 +    def __init__(self, repos):
 +        self.repos = repos
 +
 +    def get_revision_id(self, revnum, path, mapping, revprops, fileprops):
 +        # See if there is a bzr:revision-id revprop set
 +        try:
 +            (bzr_revno, revid) = mapping.get_revision_id(path, revprops, fileprops)
 +        except SubversionException, (_, num):
 +            if num == ERR_FS_NO_SUCH_REVISION:
 +                raise NoSuchRevision(path, revnum)
 +            raise
 +
 +        # Or generate it
 +        if revid is None:
++            return mapping.revision_id_foreign_to_bzr((self.repos.uuid, revnum, path))
 +
 +        return revid
 +
 +    def get_branch_revnum(self, revid, layout, project=None):
 +        """Find the (branch, revnum) tuple for a revision id."""
 +        # Try a simple parse
 +        try:
 +            (uuid, branch_path, revnum, mapping) = parse_revision_id(revid)
 +            assert isinstance(branch_path, str)
 +            assert isinstance(mapping, BzrSvnMapping)
 +            if uuid == self.repos.uuid:
 +                return (branch_path, revnum, mapping)
 +            # If the UUID doesn't match, this may still be a valid revision
 +            # id; a revision from another SVN repository may be pushed into 
 +            # this one.
 +        except InvalidRevisionId:
 +            pass
 +
 +        for entry_revid, branch, revno, mapping in self.discover_revids(layout, 0, self.repos.get_latest_revnum(), project):
 +            if revid == entry_revid:
 +                (bp, revnum, mapping_name) = self.bisect_revid_revnum(revid, branch, 0, revno)
 +                return (bp, revnum, parse_mapping_name(mapping_name))
 +        raise NoSuchRevision(self, revid)
 +
 +    def discover_revids(self, layout, from_revnum, to_revnum, project=None):
 +        reuse_policy = self.repos.get_config().get_reuse_revisions()
 +        assert reuse_policy in ("other-branches", "removed-branches", "none") 
 +        check_removed = (reuse_policy == "removed-branches")
 +        for (branch, revno, exists) in self.repos.find_fileprop_paths(layout, from_revnum, to_revnum, project, check_removed=check_removed, find_branches=True, find_tags=True):
 +            assert isinstance(branch, str)
 +            assert isinstance(revno, int)
 +            # Look at their bzr:revision-id-vX
 +            revids = set()
 +            try:
 +                props = self.repos.branchprop_list.get_properties(branch, revno)
 +                for propname, propvalue in props.items():
 +                    if not propname.startswith(SVN_PROP_BZR_REVISION_ID):
 +                        continue
 +                    mapping_name = propname[len(SVN_PROP_BZR_REVISION_ID):]
 +                    for line in propvalue.splitlines():
 +                        try:
 +                            revids.add((parse_revid_property(line), mapping_name))
 +                        except InvalidPropertyValue, ie:
 +                            mutter(str(ie))
 +            except SubversionException, (_, ERR_FS_NOT_DIRECTORY):
 +                    continue
 +
 +            # If there are any new entries that are not yet in the cache, 
 +            # add them
 +            for ((entry_revno, entry_revid), mapping_name) in revids:
 +                yield (entry_revid, branch, revno, parse_mapping_name(mapping_name))
 +
 +    def bisect_revid_revnum(self, revid, branch_path, min_revnum, max_revnum):
 +        """Find out what the actual revnum was that corresponds to a revid.
 +
 +        :param revid: Revision id to search for
 +        :param branch_path: Branch path at which to start searching
 +        :param min_revnum: Last revnum to check
 +        :param max_revnum: First revnum to check
 +        :return: Tuple with actual branchpath, revnum and mapping
 +        """
 +        assert min_revnum <= max_revnum
 +        # Find the branch property between min_revnum and max_revnum that 
 +        # added revid
 +        for revmeta in self.repos.iter_reverse_branch_changes(branch_path, max_revnum, min_revnum):
 +            for propname, propvalue in revmeta.fileprops.items():
 +                if not propname.startswith(SVN_PROP_BZR_REVISION_ID):
 +                    continue
 +                try:
 +                    (entry_revno, entry_revid) = parse_revid_property(
 +                        propvalue.splitlines()[-1])
 +                except InvalidPropertyValue:
 +                    # Don't warn about encountering an invalid property, 
 +                    # that will already have happened earlier
 +                    continue
 +                if entry_revid == revid:
 +                    mapping_name = propname[len(SVN_PROP_BZR_REVISION_ID):]
 +                    mapping = parse_mapping_name(mapping_name)
 +                    assert (mapping.is_tag(revmeta.branch_path) or 
 +                            mapping.is_branch(revmeta.branch_path))
 +                    return (revmeta.branch_path, revmeta.revnum, mapping)
 +
 +        raise InvalidBzrSvnRevision(revid)
 +
 +
 +class CachingRevidMap(object):
 +    def __init__(self, actual, cachedb=None):
 +        self.cache = RevisionIdMapCache(cachedb)
 +        self.actual = actual
 +        self.revid_seen = set()
 +
 +    def get_revision_id(self, revnum, path, mapping, changed_fileprops, revprops):
 +        # Look in the cache to see if it already has a revision id
 +        revid = self.cache.lookup_branch_revnum(revnum, path, mapping.name)
 +        if revid is not None:
 +            return revid
 +
 +        revid = self.actual.get_revision_id(revnum, path, mapping, changed_fileprops, revprops)
 +
 +        self.cache.insert_revid(revid, path, revnum, revnum, mapping.name)
 +        self.cache.commit_conditionally()
 +
 +        return revid
 +
 +    def get_branch_revnum(self, revid, layout, project=None):
 +        # Try a simple parse
 +        try:
 +            (uuid, branch_path, revnum, mapping) = parse_revision_id(revid)
 +            assert isinstance(branch_path, str)
 +            assert isinstance(mapping, BzrSvnMapping)
 +            if uuid == self.actual.repos.uuid:
 +                return (branch_path, revnum, mapping)
 +            # If the UUID doesn't match, this may still be a valid revision
 +            # id; a revision from another SVN repository may be pushed into 
 +            # this one.
 +        except InvalidRevisionId:
 +            pass
 +
 +        # Check the record out of the cache, if it exists
 +        try:
 +            (branch_path, min_revnum, max_revnum, \
 +                    mapping) = self.cache.lookup_revid(revid)
 +            assert isinstance(branch_path, str)
 +            assert isinstance(mapping, str)
 +            # Entry already complete?
 +            assert min_revnum <= max_revnum
 +            if min_revnum == max_revnum:
 +                return (branch_path, min_revnum, parse_mapping_name(mapping))
 +        except NoSuchRevision, e:
 +            last_revnum = self.actual.repos.get_latest_revnum()
 +            last_checked = self.cache.last_revnum_checked(repr((layout, project)))
 +            if (last_revnum <= last_checked):
 +                # All revision ids in this repository for the current 
 +                # layout have already been discovered. No need to 
 +                # check again.
 +                raise e
 +            found = False
 +            for entry_revid, branch, revno, mapping in self.actual.discover_revids(layout, last_checked, last_revnum, project):
 +                if entry_revid == revid:
 +                    found = True
 +                if entry_revid not in self.revid_seen:
 +                    self.cache.insert_revid(entry_revid, branch, last_checked, revno, mapping.name)
 +                    self.revid_seen.add(entry_revid)
 +                
 +            # We've added all the revision ids for this layout in the
 +            # repository, so no need to check again unless new revisions got 
 +            # added
 +            self.cache.set_last_revnum_checked(repr((layout, project)), last_revnum)
 +            if not found:
 +                raise e
 +            (branch_path, min_revnum, max_revnum, mapping) = self.cache.lookup_revid(revid)
 +            assert min_revnum <= max_revnum
 +            assert isinstance(branch_path, str)
 +
 +        (branch_path, revnum, mapping) = self.actual.bisect_revid_revnum(revid, 
 +            branch_path, min_revnum, max_revnum)
 +        self.cache.insert_revid(revid, branch_path, revnum, revnum, mapping.name)
 +        return (branch_path, revnum, mapping)
 +
 +
 +
 +class RevisionIdMapCache(CacheTable):
 +    """Revision id mapping store. 
 +
 +    Stores mapping from revid -> (path, revnum, mapping)
 +    """
 +    def _create_table(self):
 +        self.cachedb.executescript("""
 +        create table if not exists revmap (revid text, path text, min_revnum integer, max_revnum integer, mapping text);
 +        create index if not exists revid on revmap (revid);
 +        create unique index if not exists revid_path_mapping on revmap (revid, path, mapping);
 +        drop index if exists lookup_branch_revnum;
 +        create index if not exists lookup_branch_revnum_non_unique on revmap (max_revnum, min_revnum, path, mapping);
 +        create table if not exists revids_seen (layout text, max_revnum int);
 +        create unique index if not exists layout on revids_seen (layout);
 +        """)
 +        # Revisions ids are quite expensive
 +        self._commit_interval = 100
 +
 +    def set_last_revnum_checked(self, layout, revnum):
 +        """Remember the latest revision number that has been checked
 +        for a particular layout.
 +
 +        :param layout: Repository layout.
 +        :param revnum: Revision number.
 +        """
 +        self.cachedb.execute("replace into revids_seen (layout, max_revnum) VALUES (?, ?)", (layout, revnum))
 +        self.commit_conditionally()
 +
 +    def last_revnum_checked(self, layout):
 +        """Retrieve the latest revision number that has been checked 
 +        for revision ids for a particular layout.
 +
 +        :param layout: Repository layout.
 +        :return: Last revision number checked or 0.
 +        """
 +        self.mutter("last revnum checked %r", layout)
 +        ret = self.cachedb.execute(
 +            "select max_revnum from revids_seen where layout = ?", (layout,)).fetchone()
 +        if ret is None:
 +            return 0
 +        return int(ret[0])
 +    
 +    def lookup_revid(self, revid):
 +        """Lookup the details for a particular revision id.
 +
 +        :param revid: Revision id.
 +        :return: Tuple with path inside repository, minimum revision number, maximum revision number and 
 +            mapping.
 +        """
 +        assert isinstance(revid, str)
 +        self.mutter("lookup revid %r", revid)
 +        ret = self.cachedb.execute(
 +            "select path, min_revnum, max_revnum, mapping from revmap where revid=? order by abs(min_revnum-max_revnum) asc", (revid,)).fetchone()
 +        if ret is None:
 +            raise NoSuchRevision(self, revid)
 +        (path, min_revnum, max_revnum, mapping) = (ret[0].encode("utf-8"), int(ret[1]), int(ret[2]), ret[3].encode("utf-8"))
 +        if min_revnum > max_revnum:
 +            return (path, max_revnum, min_revnum, mapping)
 +        else:
 +            return (path, min_revnum, max_revnum, mapping)
 +
 +    def lookup_branch_revnum(self, revnum, path, mapping):
 +        """Lookup a revision by revision number, branch path and mapping.
 +
 +        :param revnum: Subversion revision number.
 +        :param path: Subversion branch path.
 +        :param mapping: Mapping
 +        """
 +        assert isinstance(revnum, int)
 +        assert isinstance(path, str)
 +        assert isinstance(mapping, str)
 +        row = self.cachedb.execute(
 +                "select revid from revmap where max_revnum=? and min_revnum=? and path=? and mapping=?", (revnum, revnum, path, mapping)).fetchone()
 +        if row is not None:
 +            ret = str(row[0])
 +        else:
 +            ret = None
 +        self.mutter("lookup branch,revnum %r:%r -> %r", path, revnum, ret)
 +        return ret
 +
 +    def insert_revid(self, revid, branch, min_revnum, max_revnum, mapping):
 +        """Insert a revision id into the revision id cache.
 +
 +        :param revid: Revision id for which to insert metadata.
 +        :param branch: Branch path at which the revision was seen
 +        :param min_revnum: Minimum Subversion revision number in which the 
 +                           revid was found
 +        :param max_revnum: Maximum Subversion revision number in which the 
 +                           revid was found
 +        :param mapping: Name of the mapping with which the revision 
 +                       was found
 +        """
 +        assert revid is not None and revid != ""
 +        assert isinstance(mapping, str)
 +        assert isinstance(branch, str)
 +        assert isinstance(min_revnum, int) and isinstance(max_revnum, int)
 +        assert min_revnum <= max_revnum
 +        self.mutter("insert revid %r:%r-%r -> %r", branch, min_revnum, max_revnum, revid)
 +        if min_revnum == max_revnum:
 +            cursor = self.cachedb.execute(
 +                "update revmap set min_revnum = ?, max_revnum = ? WHERE revid=? AND path=? AND mapping=?",
 +                (min_revnum, max_revnum, revid, branch, mapping))
 +        else:
 +            cursor = self.cachedb.execute(
 +                "update revmap set min_revnum = MAX(min_revnum,?), max_revnum = MIN(max_revnum, ?) WHERE revid=? AND path=? AND mapping=?",
 +                (min_revnum, max_revnum, revid, branch, mapping))
 +        if cursor.rowcount == 0:
 +            self.cachedb.execute(
 +                "insert into revmap (revid,path,min_revnum,max_revnum,mapping) VALUES (?,?,?,?,?)",
 +                (revid, branch, min_revnum, max_revnum, mapping))
index 6e681f12d46af1161d0cb6e7280d7b289051f3c0,0000000000000000000000000000000000000000..3d77b3acd6267cf7d260af115a9d1d9e7170166b
mode 100644,000000..100644
--- /dev/null
@@@ -1,898 -1,0 +1,898 @@@
-             mapping.generate_revision_id((uuid, 1, "trunk")),
-             mapping.generate_revision_id((uuid, 2, "trunk")),
-             mapping.generate_revision_id((uuid, 3, "trunk")),
 +# Copyright (C) 2006-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
 +
 +"""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.mapping3 import BzrSvnMappingv3FileProps
 +from bzrlib.plugins.svn.mapping3.scheme import TrunkBranchingScheme
 +from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 +
 +class WorkingSubversionBranch(TestCaseWithSubversionRepository):
 +    def test_last_rev_rev_hist(self):
 +        repos_url = self.make_repository("a")
 +        branch = Branch.open(repos_url)
 +        branch.revision_history()
 +        self.assertEqual(branch.generate_revision_id(0), branch.last_revision())
 +
 +    def test_get_branch_path_root(self):
 +        repos_url = self.make_repository("a")
 +        branch = Branch.open(repos_url)
 +        self.assertEqual("", branch.get_branch_path())
 +
 +    def test_tags_dict(self):
 +        repos_url = self.make_repository("a")
 +       
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.add_dir("tags")
 +        tags.add_dir("tags/foo")
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        b = Branch.open(repos_url + "/trunk")
 +        self.assertEquals(["foo"], b.tags.get_tag_dict().keys())
 +
 +    def test_tag_set(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk")
 +        dc.add_dir("tags")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.add_file("trunk/bla").modify()
 +        dc.close()
 +
 +        b = Branch.open(repos_url + "/trunk")
 +        b.tags.set_tag(u"mytag", b.repository.generate_revision_id(1, "trunk", b.repository.get_mapping()))
 +
 +        self.assertEquals(core.NODE_DIR, 
 +                b.repository.transport.check_path("tags/mytag", 3))
 +
 +    def test_tags_delete(self):
 +        repos_url = self.make_repository("a")
 +       
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.add_dir("tags")
 +        tags.add_dir("tags/foo")
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        b = Branch.open(repos_url + "/trunk")
 +        self.assertEquals(["foo"], b.tags.get_tag_dict().keys())
 +        b.tags.delete_tag(u"foo")
 +        b = Branch.open(repos_url + "/trunk")
 +        self.assertEquals([], b.tags.get_tag_dict().keys())
 +
 +    def test_tag_lookup(self):
 +        repos_url = self.make_repository("a")
 +       
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.add_dir("tags")
 +        tags.add_dir("tags/foo")
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        b = Branch.open(repos_url + "/trunk")
 +        self.assertEquals("", b.project)
 +        self.assertEquals(b.repository.generate_revision_id(1, "tags/foo", b.repository.get_mapping()), b.tags.lookup_tag("foo"))
 +
 +    def test_tag_lookup_nonexistant(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +       
 +        b = Branch.open(repos_url + "/trunk")
 +        self.assertRaises(NoSuchTag, b.tags.lookup_tag, "foo")
 +
 +    def test_tags_delete_nonexistent(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +       
 +        b = Branch.open(repos_url + "/trunk")
 +        self.assertRaises(NoSuchTag, b.tags.delete_tag, u"foo")
 +
 +    def test_get_branch_path_old(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk2", "trunk", 1)
 +        dc.close()
 +
 +        branch = Branch.open(urlutils.join(repos_url, "trunk2"))
 +        self.assertEqual("trunk2", branch.get_branch_path(2))
 +        self.assertEqual("trunk", branch.get_branch_path(1))
 +
 +    def test_get_branch_path_subdir(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        branch = Branch.open(repos_url+"/trunk")
 +        self.assertEqual("trunk", branch.get_branch_path())
 +
 +    def test_open_nonexistant(self):
 +        repos_url = self.make_repository("a")
 +        self.assertRaises(NotBranchError, Branch.open, repos_url + "/trunk")
 +
 +    def test_last_rev_rev_info(self):
 +        repos_url = self.make_repository("a")
 +        branch = Branch.open(repos_url)
 +        self.assertEqual((1, branch.generate_revision_id(0)),
 +                branch.last_revision_info())
 +        branch.revision_history()
 +        self.assertEqual((1, branch.generate_revision_id(0)),
 +                branch.last_revision_info())
 +
 +    def test_lookup_revision_id_unknown(self):
 +        repos_url = self.make_repository("a")
 +        branch = Branch.open(repos_url)
 +        self.assertRaises(NoSuchRevision, 
 +                lambda: branch.lookup_revision_id("bla"))
 +
 +    def test_lookup_revision_id(self):
 +        repos_url = self.make_repository("a")
 +        branch = Branch.open(repos_url)
 +        self.assertEquals(0, 
 +                branch.lookup_revision_id(branch.last_revision()))
 +
 +    def test_set_parent(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        branch.set_parent("foobar")
 +
 +    def test_num_revnums(self):
 +        repos_url = self.make_repository('a')
 +        bzrdir = BzrDir.open(repos_url)
 +        branch = bzrdir.open_branch()
 +        self.assertEqual(branch.generate_revision_id(0),
 +                         branch.last_revision())
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify()
 +        dc.close()
 +        
 +        bzrdir = BzrDir.open(repos_url)
 +        branch = bzrdir.open_branch()
 +        repos = bzrdir.find_repository()
 +        
 +        mapping = repos.get_mapping()
 +
 +        self.assertEqual(repos.generate_revision_id(1, "", mapping), 
 +                branch.last_revision())
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("foo").modify()
 +        dc.close()
 +
 +        branch = Branch.open(repos_url)
 +        repos = Repository.open(repos_url)
 +
 +        self.assertEqual(repos.generate_revision_id(2, "", mapping),
 +                branch.last_revision())
 +
 +    def test_set_revision_history_empty(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertRaises(NotImplementedError, branch.set_revision_history, [])
 +
 +    def test_set_revision_history_ghost(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file('trunk/foo').modify()
 +        dc.close()
 +
 +        branch = Branch.open(repos_url+"/trunk")
 +        self.assertRaises(NotImplementedError, branch.set_revision_history, ["nonexistantt"])
 +
 +    def test_set_revision_history(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file('trunk/foo').modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.add_file('trunk/bla').modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.add_file('trunk/bar').modify()
 +        dc.close()
 +
 +        branch = Branch.open(repos_url+"/trunk")
 +        orig_history = branch.revision_history()
 +        branch.set_revision_history(orig_history[:-1])
 +        self.assertEquals(orig_history[:-1], branch.revision_history())
 +
 +    def test_break_lock(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        branch.control_files.break_lock()
 +
 +    def test_repr(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertEqual("SvnBranch('%s')" % repos_url, branch.__repr__())
 +
 +    def test_get_physical_lock_status(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertFalse(branch.get_physical_lock_status())
 +
 +    def test_set_push_location(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertRaises(NotImplementedError, branch.set_push_location, [])
 +
 +    def test_get_parent(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertEqual(None, branch.get_parent())
 +
 +    def test_get_push_location(self):
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertIs(None, branch.get_push_location())
 +
 +    def test_revision_history(self):
 +        repos_url = self.make_repository('a')
 +
 +        branch = Branch.open(repos_url)
 +        self.assertEqual([branch.generate_revision_id(0)], 
 +                branch.revision_history())
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify()
 +        dc.change_prop(SVN_PROP_BZR_REVISION_ID+"v3-none", 
 +                "42 mycommit\n")
 +        dc.close()
 +        
 +        branch = Branch.open(repos_url)
 +        repos = Repository.open(repos_url)
 +        
 +        mapping = repos.get_mapping()
 +
 +        self.assertEqual([repos.generate_revision_id(0, "", mapping), 
 +                    repos.generate_revision_id(1, "", mapping)], 
 +                branch.revision_history())
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("foo").modify()
 +        dc.close()
 +
 +        branch = Branch.open(repos_url)
 +        repos = Repository.open(repos_url)
 +
 +        mapping = repos.get_mapping()
 +
 +        self.assertEqual([
 +            repos.generate_revision_id(0, "", mapping),
 +            "mycommit",
 +            repos.generate_revision_id(2, "", mapping)],
 +            branch.revision_history())
 +
 +    def test_revision_id_to_revno_none(self):
 +        """The None revid should map to revno 0."""
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertEquals(0, branch.revision_id_to_revno(NULL_REVISION))
 +
 +    def test_revision_id_to_revno_nonexistant(self):
 +        """revision_id_to_revno() should raise NoSuchRevision if
 +        the specified revision did not exist in the branch history."""
 +        repos_url = self.make_repository('a')
 +        branch = Branch.open(repos_url)
 +        self.assertRaises(NoSuchRevision, branch.revision_id_to_revno, "bla")
 +    
 +    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"))
 +
 +    def test_get_nick_none(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify()
 +        dc.close()
 +
 +        branch = Branch.open(repos_url)
 +
 +        self.assertEquals("a", branch.nick)
 +
 +    def test_get_nick_path(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        branch = Branch.open(repos_url+"/trunk")
 +
 +        self.assertEqual("trunk", branch.nick)
 +
 +    def test_get_revprops(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify()
 +        dc.change_prop("bzr:revision-info", 
 +                "properties: \n\tbranch-nick: mybranch\n")
 +        dc.close()
 +
 +        branch = Branch.open(repos_url)
 +
 +        rev = branch.repository.get_revision(branch.last_revision())
 +
 +        self.assertEqual("mybranch", rev.properties["branch-nick"])
 +
 +    def test_fetch_replace(self):
 +        filename = os.path.join(self.test_dir, "dumpfile")
 +        open(filename, 'w').write("""SVN-fs-dump-format-version: 2
 +
 +UUID: 6f95bc5c-e18d-4021-aca8-49ed51dbcb75
 +
 +Revision-number: 0
 +Prop-content-length: 56
 +Content-length: 56
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:25.270824Z
 +PROPS-END
 +
 +Revision-number: 1
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:26.117512Z
 +PROPS-END
 +
 +Node-path: trunk
 +Node-kind: dir
 +Node-action: add
 +Prop-content-length: 10
 +Content-length: 10
 +
 +PROPS-END
 +
 +
 +Node-path: trunk/hosts
 +Node-kind: file
 +Node-action: add
 +Prop-content-length: 10
 +Text-content-length: 4
 +Text-content-md5: 771ec3328c29d17af5aacf7f895dd885
 +Content-length: 14
 +
 +PROPS-END
 +hej1
 +
 +Revision-number: 2
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:27.130044Z
 +PROPS-END
 +
 +Node-path: trunk/hosts
 +Node-kind: file
 +Node-action: change
 +Text-content-length: 4
 +Text-content-md5: 6c2479dbb342b8df96d84db7ab92c412
 +Content-length: 4
 +
 +hej2
 +
 +Revision-number: 3
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:28.114350Z
 +PROPS-END
 +
 +Node-path: trunk/hosts
 +Node-kind: file
 +Node-action: change
 +Text-content-length: 4
 +Text-content-md5: 368cb8d3db6186e2e83d9434f165c525
 +Content-length: 4
 +
 +hej3
 +
 +Revision-number: 4
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:29.129563Z
 +PROPS-END
 +
 +Node-path: branches
 +Node-kind: dir
 +Node-action: add
 +Prop-content-length: 10
 +Content-length: 10
 +
 +PROPS-END
 +
 +
 +Revision-number: 5
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:31.130508Z
 +PROPS-END
 +
 +Node-path: branches/foobranch
 +Node-kind: dir
 +Node-action: add
 +Node-copyfrom-rev: 4
 +Node-copyfrom-path: trunk
 +
 +
 +Revision-number: 6
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:33.129149Z
 +PROPS-END
 +
 +Node-path: branches/foobranch/hosts
 +Node-kind: file
 +Node-action: delete
 +
 +Node-path: branches/foobranch/hosts
 +Node-kind: file
 +Node-action: add
 +Node-copyfrom-rev: 2
 +Node-copyfrom-path: trunk/hosts
 +
 +
 +
 +
 +Revision-number: 7
 +Prop-content-length: 94
 +Content-length: 94
 +
 +K 7
 +svn:log
 +V 0
 +
 +K 10
 +svn:author
 +V 0
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-30T12:41:34.136423Z
 +PROPS-END
 +
 +Node-path: branches/foobranch/hosts
 +Node-kind: file
 +Node-action: change
 +Text-content-length: 8
 +Text-content-md5: 0e328d3517a333a4879ebf3d88fd82bb
 +Content-length: 8
 +
 +foohosts""")
 +        os.mkdir("new")
 +        os.mkdir("old")
 +
 +        load_dumpfile("dumpfile", "old")
 +
 +        url = "old/branches/foobranch"
 +        mutter('open %r' % url)
 +        olddir = BzrDir.open(url)
 +
 +        newdir = olddir.sprout("new")
 +
 +        newbranch = newdir.open_branch()
 +
 +        oldbranch = Branch.open(url)
 +
 +        uuid = "6f95bc5c-e18d-4021-aca8-49ed51dbcb75"
 +        newbranch.lock_read()
 +        tree = newbranch.repository.revision_tree(oldbranch.generate_revision_id(7))
 +
 +        host_fileid = tree.inventory.path2id("hosts")
 +
 +        self.assertVersionsPresentEquals(newbranch.repository.texts, 
 +                                        host_fileid, [
 +            oldbranch.generate_revision_id(6),
 +            oldbranch.generate_revision_id(7)])
 +        newbranch.unlock()
 + 
 +
 +    def test_fetch_odd(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/hosts").modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.open_file("trunk/hosts").modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("trunk/hosts").modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("branches")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.open_dir("branches")
 +        branches.add_dir("branches/foobranch", "trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.open_dir("branches")
 +        foobranch = branches.open_dir("branches/foobranch")
 +        foobranch.open_file("branches/foobranch/hosts").modify()
 +        dc.close()
 +
 +        os.mkdir("new")
 +
 +        url = repos_url+"/branches/foobranch"
 +        mutter('open %r' % url)
 +        olddir = BzrDir.open(url)
 +
 +        newdir = olddir.sprout("new")
 +
 +        newbranch = newdir.open_branch()
 +        oldbranch = olddir.open_branch()
 +
 +        uuid = olddir.find_repository().uuid
 +        tree = newbranch.repository.revision_tree(
 +             oldbranch.generate_revision_id(6))
 +        transaction = newbranch.repository.get_transaction()
 +        newbranch.repository.lock_read()
 +        texts = newbranch.repository.texts
 +        host_fileid = tree.inventory.path2id("hosts")
 +        mapping = BzrSvnMappingv3FileProps(TrunkBranchingScheme())
 +        self.assertVersionsPresentEquals(texts, host_fileid, [
++            mapping.revision_id_foreign_to_bzr((uuid, 1, "trunk")),
++            mapping.revision_id_foreign_to_bzr((uuid, 2, "trunk")),
++            mapping.revision_id_foreign_to_bzr((uuid, 3, "trunk")),
 +            oldbranch.generate_revision_id(6)])
 +        newbranch.repository.unlock()
 +
 +    def assertVersionsPresentEquals(self, texts, fileid, versions):
 +        self.assertEqual(set([(fileid, v) for v in versions]),
 +            set(filter(lambda (fid, rid): fid == fileid, texts.keys())))
 +
 +    def test_check(self):
 +        self.make_repository('d')
 +        branch = Branch.open('d')
 +        result = branch.check()
 +        self.assertEqual(branch, result.branch) 
 + 
 +    def test_generate_revision_id(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        bla = dc.add_dir("bla")
 +        bla.add_dir("bla/bloe")
 +        dc.close()
 +
 +        branch = Branch.open('d')
 +        self.assertEqual("svn-v3-none:%s::1" % (branch.repository.uuid),  branch.generate_revision_id(1))
 +
 +    def test_create_checkout(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/hosts").modify()
 +        dc.close()
 +
 +        url = repos_url+"/trunk"
 +        oldbranch = Branch.open(url)
 +
 +        newtree = oldbranch.create_checkout("e")
 +        self.assertTrue(newtree.branch.repository.has_revision(
 +           oldbranch.generate_revision_id(1)))
 +
 +        self.assertTrue(os.path.exists("e/.bzr"))
 +        self.assertFalse(os.path.exists("e/.svn"))
 +
 +    def test_create_checkout_lightweight(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/hosts")
 +        dc.close()
 +
 +        oldbranch = Branch.open(repos_url+"/trunk")
 +        newtree = oldbranch.create_checkout("e", lightweight=True)
 +        self.assertEqual(oldbranch.generate_revision_id(1), newtree.base_revid)
 +        self.assertTrue(os.path.exists("e/.svn"))
 +        self.assertFalse(os.path.exists("e/.bzr"))
 +
 +    def test_create_checkout_lightweight_stop_rev(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/hosts").modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.open_file("trunk/hosts").modify()
 +        dc.close()
 +
 +        url = repos_url+"/trunk"
 +        oldbranch = Branch.open(url)
 +
 +        newtree = oldbranch.create_checkout("e", revision_id=
 +           oldbranch.generate_revision_id(1), lightweight=True)
 +        self.assertEqual(oldbranch.generate_revision_id(1),
 +           newtree.base_revid)
 +        self.assertTrue(os.path.exists("e/.svn"))
 +        self.assertFalse(os.path.exists("e/.bzr"))
 +
 +    def test_fetch_branch(self):
 +        repos_url = self.make_client('d', 'sc')
 +
 +        sc = self.get_commit_editor(repos_url)
 +        foo = sc.add_dir("foo")
 +        foo.add_file("foo/bla").modify()
 +        sc.close()
 +
 +        olddir = BzrDir.open("sc")
 +
 +        os.mkdir("dc")
 +        
 +        newdir = olddir.sprout('dc')
 +
 +        self.assertEqual(
 +                olddir.open_branch().last_revision(),
 +                newdir.open_branch().last_revision())
 +
 +    def test_fetch_dir_upgrade(self):
 +        repos_url = self.make_client('d', 'sc')
 +
 +        sc = self.get_commit_editor(repos_url)
 +        trunk = sc.add_dir("trunk")
 +        mylib = trunk.add_dir("trunk/mylib")
 +        mylib.add_file("trunk/mylib/bla").modify()
 +        sc.add_dir("branches")
 +        sc.close()
 +
 +        sc = self.get_commit_editor(repos_url)
 +        branches = sc.open_dir("branches")
 +        branches.add_dir("branches/abranch", "trunk/mylib")
 +        sc.close()
 +
 +        self.client_update('sc')
 +        olddir = BzrDir.open("sc/branches/abranch")
 +
 +        os.mkdir("dc")
 +        
 +        newdir = olddir.sprout('dc')
 +
 +        self.assertEqual(
 +                olddir.open_branch().last_revision(),
 +                newdir.open_branch().last_revision())
 +
 +    def test_fetch_branch_downgrade(self):
 +        repos_url = self.make_client('d', 'sc')
 +
 +        sc = self.get_commit_editor(repos_url)
 +        sc.add_dir("trunk")
 +        branches = sc.add_dir("branches")
 +        abranch = branches.add_dir("branches/abranch")
 +        abranch.add_file("branches/abranch/bla").modify()
 +        sc.close()
 +
 +        sc = self.get_commit_editor(repos_url)
 +        trunk = sc.open_dir("trunk")
 +        sc.add_dir("trunk/mylib", "branches/abranch")
 +        sc.close()
 +
 +        self.client_update('sc')
 +        olddir = BzrDir.open("sc/trunk")
 +
 +        os.mkdir("dc")
 +        
 +        newdir = olddir.sprout('dc')
 +
 +        self.assertEqual(
 +                olddir.open_branch().last_revision(),
 +                newdir.open_branch().last_revision())
 +
 +
 +
 +    def test_ghost_workingtree(self):
 +        # Looks like bazaar has trouble creating a working tree of a 
 +        # revision that has ghost parents
 +        repos_url = self.make_client('d', 'sc')
 +
 +        sc = self.get_commit_editor(repos_url)
 +        foo = sc.add_dir("foo")
 +        foo.add_file("foo/bla").modify()
 +        sc.change_prop("bzr:ancestry:v3-none", "some-ghost\n")
 +        sc.close()
 +
 +        olddir = BzrDir.open("sc")
 +
 +        os.mkdir("dc")
 +        
 +        newdir = olddir.sprout('dc')
 +        newdir.find_repository().get_revision(
 +                newdir.open_branch().last_revision())
 +        newdir.find_repository().get_revision_inventory(
 +                newdir.open_branch().last_revision())
 +
 +
 +class TestFakeControlFiles(TestCase):
 +    def test_get_utf8(self):
 +        f = FakeControlFiles()
 +        self.assertRaises(NoSuchFile, f.get_utf8, "foo")
 +
 +
 +    def test_get(self):
 +        f = FakeControlFiles()
 +        self.assertRaises(NoSuchFile, f.get, "foobla")
 +
 +
 +class BranchFormatTests(TestCase):
 +    def setUp(self):
 +        self.format = SvnBranchFormat()
 +
 +    def test_initialize(self):
 +        self.assertRaises(NotImplementedError, self.format.initialize, None)
 +
 +    def test_get_format_string(self):
 +        self.assertEqual("Subversion Smart Server", 
 +                         self.format.get_format_string())
 +
 +    def test_get_format_description(self):
 +        self.assertEqual("Subversion Smart Server", 
 +                         self.format.get_format_description())
index 75e4bde3f8fcbb3b85d315ba97e55bad44e44e86,0000000000000000000000000000000000000000..ae9c7f29fe2521704d13a3bbfd78fceaf8fc523d
mode 100644,000000..100644
--- /dev/null
@@@ -1,418 -1,0 +1,418 @@@
-         ret = self.mapping.parse_revision_id(revid)
 +# Copyright (C) 2006-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
 +
 +"""File id tests."""
 +
 +from bzrlib.bzrdir import BzrDir
 +from bzrlib.repository import Repository
 +from bzrlib.trace import mutter
 +from bzrlib.tests import TestCase
 +
 +from bzrlib.plugins.svn.fileids import simple_apply_changes
 +from bzrlib.plugins.svn.mapping3 import BzrSvnMappingv3FileProps, set_branching_scheme
 +from bzrlib.plugins.svn.mapping3.scheme import TrunkBranchingScheme, NoBranchingScheme
 +from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 +
 +class MockRepo(object):
 +    def __init__(self, mapping, uuid="uuid"):
 +        self.uuid = uuid
 +
 +    def lookup_revision_id(self, revid):
++        ret = self.mapping.revision_id_foreign_to_bzr(revid)
 +        return ret[1], ret[2], ret[3]
 +
 +
 +class TestComplexFileids(TestCaseWithSubversionRepository):
 +    # branchtagcopy.dump
 +    # changeaftercp.dump
 +    # combinedbranch.dump
 +    # executable.dump
 +    # ignore.dump
 +    # inheritance.dump
 +    # movebranch.dump
 +    # movefileorder.dump
 +    # recreatebranch.dump
 +    def test_simplemove(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("bar", "foo", 1)
 +        dc.delete("foo")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("bar").modify("data2")
 +        dc.close()
 +
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +
 +        inv1 = repository.get_inventory(
 +                repository.generate_revision_id(1, "", mapping))
 +        inv2 = repository.get_inventory(
 +                repository.generate_revision_id(2, "", mapping))
 +        mutter('inv1: %r' % inv1.entries())
 +        mutter('inv2: %r' % inv2.entries())
 +        self.assertNotEqual(None, inv1.path2id("foo"))
 +        self.assertIs(None, inv2.path2id("foo"))
 +        self.assertNotEqual(None, inv2.path2id("bar"))
 +        self.assertNotEqual(inv1.path2id("foo"), inv2.path2id("blie"))
 +        self.assertNotEqual(inv2.path2id("bar"), inv2.path2id("blie"))
 +
 +    def test_simplecopy(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify("data")
 +        dc.add_file("blie").modify("bloe")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("bar", "foo", 1).modify("data2")
 +        dc.close()
 +
 +        bzrdir = BzrDir.open("svn+%s" % repos_url)
 +        repository = bzrdir.find_repository()
 +
 +        mapping = repository.get_mapping()
 +
 +        inv1 = repository.get_inventory(
 +                repository.generate_revision_id(1, "", mapping))
 +        inv2 = repository.get_inventory(
 +                repository.generate_revision_id(2, "", mapping))
 +        self.assertNotEqual(inv1.path2id("foo"), inv2.path2id("bar"))
 +        self.assertNotEqual(inv1.path2id("foo"), inv2.path2id("blie"))
 +        self.assertIs(None, inv1.path2id("bar"))
 +        self.assertNotEqual(None, inv1.path2id("blie"))
 +
 +    def test_simpledelete(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.delete("foo")
 +        dc.close()
 +
 +        bzrdir = BzrDir.open("svn+%s" % repos_url)
 +        repository = bzrdir.find_repository()
 +        mapping = repository.get_mapping()
 +
 +        inv1 = repository.get_inventory(
 +                repository.generate_revision_id(1, "", mapping))
 +        inv2 = repository.get_inventory(
 +                repository.generate_revision_id(2, "", mapping))
 +        self.assertNotEqual(None, inv1.path2id("foo"))
 +        self.assertIs(None, inv2.path2id("foo"))
 +
 +    def test_replace(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.delete("foo")
 +        dc.add_file("foo").modify("data")
 +        dc.close()
 +
 +        bzrdir = BzrDir.open(repos_url)
 +        repository = bzrdir.find_repository()
 +
 +        mapping = repository.get_mapping()
 +
 +        inv1 = repository.get_inventory(
 +                repository.generate_revision_id(1, "", mapping))
 +        inv2 = repository.get_inventory(
 +                repository.generate_revision_id(2, "", mapping))
 +        self.assertNotEqual(inv1.path2id("foo"), inv2.path2id("foo"))
 +
 +    def test_copy_branch(self):
 +        scheme = TrunkBranchingScheme()
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        dir = trunk.add_dir("trunk/dir")
 +        dir.add_file("trunk/dir/file").modify("data")
 +        dc.add_dir("branches")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.open_dir("branches")
 +        branches.add_dir("branches/mybranch", "trunk", 1)
 +        dc.close()
 +
 +        bzrdir = BzrDir.open(repos_url + "/branches/mybranch")
 +        repository = bzrdir.find_repository()
 +
 +        mapping = repository.get_mapping()
 +
 +        inv1 = repository.get_inventory(
 +                repository.generate_revision_id(1, "trunk", mapping))
 +        inv2 = repository.get_inventory(
 +                repository.generate_revision_id(2, "branches/mybranch", mapping))
 +        self.assertEqual(inv1.path2id("dir"), inv2.path2id("dir"))
 +        self.assertEqual(inv1.path2id("dir/file"), inv2.path2id("dir/file"))
 +
 +        fileid, revid = repository.get_fileid_map(2, 
 +                            "branches/mybranch", mapping)["dir/file"]
 +        self.assertEqual(fileid, inv1.path2id("dir/file"))
 +        self.assertEqual(repository.generate_revision_id(1, "trunk", mapping), revid)
 +
 +
 +class TestFileMapping(TestCase):
 +    def setUp(self):
 +        self.mapping = BzrSvnMappingv3FileProps(NoBranchingScheme())
 +
 +    def apply_mappings(self, mappings, find_children=None, renames={}):
 +        map = {}
 +        brns = mappings.keys()
 +        brns.sort()
 +        for r in brns:
 +            (revnum, branchpath) = r
 +            def new_file_id(x):
 +                if renames.has_key(r) and renames[r].has_key(x):
 +                    return renames[r][x]
 +                return self.mapping.generate_file_id("uuid", revnum, branchpath, x)
 +            revmap = simple_apply_changes(new_file_id, mappings[r], find_children)
 +            map.update(dict([(x, (revmap[x], r)) for x in revmap]))
 +        return map
 +
 +    def test_simple(self):
 +        map = self.apply_mappings({(1, ""): {"foo": ('A', None, None)}})
 +        self.assertEqual({ 'foo': ("1@uuid::foo",
 +                                       (1, ""))
 +                         }, map)
 +
 +    def test_simple_add(self):
 +        map = self.apply_mappings({(1, ""): {"": ('A', None, None), "foo": ('A', None, None)}})
 +        self.assertEqual({
 +            '': ('1@uuid::', (1, "")),
 +            'foo': ("1@uuid::foo", (1, "")) 
 +            }, map)
 +
 +    def test_copy(self):
 +        def find_children(path, revid):
 +            if path == "foo":
 +                yield "foo/blie"
 +                yield "foo/bla"
 +        map = self.apply_mappings({
 +                (1, ""): {
 +                                   "foo": ('A', None, None), 
 +                                   "foo/blie": ('A', None, None),
 +                                   "foo/bla": ('A', None, None)},
 +                (2, ""): {
 +                                   "foob": ('A', 'foo', 1), 
 +                                   "foob/bla": ('M', None, None)}
 +                }, find_children)
 +        self.assertTrue(map.has_key("foob/bla"))
 +        self.assertTrue(map.has_key("foob/blie"))
 +
 +    def test_touchparent(self):
 +        map = self.apply_mappings(
 +                {(1, ""): {
 +                                   "foo": ('A', None, None), 
 +                                   "foo/bla": ('A', None, None)},
 +                 (2, ""): {
 +                                   "foo/bla": ('M', None, None)}
 +                })
 +        self.assertEqual((1, ""), 
 +                         map["foo"][1])
 +        self.assertEqual((1, ""), 
 +                         map["foo/bla"][1])
 +
 +    def test_usemap(self):
 +        map = self.apply_mappings(
 +                {(1, ""): {
 +                                   "foo": ('A', None, None), 
 +                                   "foo/bla": ('A', None, None)},
 +                 (2, ""): {
 +                                   "foo/bla": ('M', None, None)}
 +                 }, 
 +                renames={(1, ""): {"foo": "myid"}})
 +        self.assertEqual("myid", map["foo"][0])
 +
 +    def test_usemap_later(self):
 +        map = self.apply_mappings(
 +                {(1, ""): {
 +                                   "foo": ('A', None, None), 
 +                                   "foo/bla": ('A', None, None)},
 +                 (2, ""): {
 +                                   "foo/bla": ('M', None, None)}
 +                 }, 
 +                renames={(2, ""): {"foo": "myid"}})
 +        self.assertEqual("1@uuid::foo", map["foo"][0])
 +        self.assertEqual((1, ""), map["foo"][1])
 +
 +
 +class GetMapTests(TestCaseWithSubversionRepository):
 +    def setUp(self):
 +        super(GetMapTests, self).setUp()
 +        self.repos_url = self.make_repository("d")
 +        self.repos = Repository.open(self.repos_url)
 +
 +    def test_empty(self):
 +        set_branching_scheme(self.repos, NoBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +        self.assertEqual({"": (self.mapping.generate_file_id(self.repos.uuid, 0, "", u""), self.repos.generate_revision_id(0, "", self.mapping))}, 
 +                         self.repos.get_fileid_map(0, "", self.mapping))
 +
 +    def test_empty_trunk(self):
 +        set_branching_scheme(self.repos, TrunkBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        self.assertEqual({"": (self.mapping.generate_file_id(self.repos.uuid, 1, "trunk", u""), self.repos.generate_revision_id(1, "trunk", self.mapping))}, 
 +                self.repos.get_fileid_map(1, "trunk", self.mapping))
 +
 +    def test_change_parent(self):
 +        set_branching_scheme(self.repos, TrunkBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +        
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.open_dir("trunk").add_file("trunk/file").modify("data")
 +        dc.close()
 +
 +        self.assertEqual({
 +            "": (self.mapping.generate_file_id(self.repos.uuid, 1, "trunk", u""), 
 +                 self.repos.generate_revision_id(1, "trunk", self.mapping)), 
 +            "file": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"file"), 
 +                     self.repos.generate_revision_id(2, "trunk", self.mapping))}, 
 +            self.repos.get_fileid_map(2, "trunk", self.mapping))
 +
 +    def test_change_updates(self):
 +        set_branching_scheme(self.repos, TrunkBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.open_dir("trunk").add_file("trunk/file").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.open_dir("trunk").open_file("trunk/file").modify("otherdata")
 +        dc.close()
 +
 +        self.assertEqual({
 +            "": (self.mapping.generate_file_id(self.repos.uuid, 1, "trunk", u""), 
 +                 self.repos.generate_revision_id(1, "trunk", self.mapping)), 
 +            "file": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"file"), 
 +                     self.repos.generate_revision_id(3, "trunk", self.mapping))}, 
 +            self.repos.get_fileid_map(3, "trunk", self.mapping))
 +
 +    def test_sibling_unrelated(self):
 +        set_branching_scheme(self.repos, TrunkBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.add_file("trunk/file").modify("data")
 +        trunk.add_file("trunk/bar").modify("data2")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.open_file("trunk/file").modify('otherdata')
 +        dc.close()
 +
 +        self.assertEqual({
 +            "": (self.mapping.generate_file_id(self.repos.uuid, 1, "trunk", u""), 
 +                 self.repos.generate_revision_id(1, "trunk", self.mapping)), 
 +            "bar": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"bar"), 
 +                    self.repos.generate_revision_id(2, "trunk", self.mapping)), 
 +            "file": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"file"), 
 +                     self.repos.generate_revision_id(3, "trunk", self.mapping))}, 
 +            self.repos.get_fileid_map(3, "trunk", self.mapping))
 +
 +    def test_copy(self):
 +        set_branching_scheme(self.repos, TrunkBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.add_file("trunk/file").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        trunk = dc.open_dir("trunk")
 +        trunk.add_file("trunk/bar", "trunk/file", 2)
 +        dc.close()
 +
 +        self.assertEqual({
 +            "": (self.mapping.generate_file_id(self.repos.uuid, 1, "trunk", u""), 
 +                 self.repos.generate_revision_id(1, "trunk", self.mapping)), 
 +            "bar": (self.mapping.generate_file_id(self.repos.uuid, 3, "trunk", u"bar"), 
 +                    self.repos.generate_revision_id(3, "trunk", self.mapping)), 
 +            "file": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"file"), 
 +                     self.repos.generate_revision_id(2, "trunk", self.mapping))}, self.repos.get_fileid_map(3, "trunk", self.mapping))
 +
 +    def test_copy_nested_modified(self):
 +        set_branching_scheme(self.repos, TrunkBranchingScheme())
 +        self.mapping = self.repos.get_mapping()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        trunk = dc.open_dir("trunk")
 +        dir = trunk.add_dir("trunk/dir")
 +        dir.add_file("trunk/dir/file").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        trunk = dc.open_dir("trunk")
 +        dir = trunk.add_dir("trunk/bar", "trunk/dir")
 +        dir.open_file("trunk/bar/file").modify("data2")
 +        dc.close()
 +
 +        self.assertEqual({
 +          "": (self.mapping.generate_file_id(self.repos.uuid, 1, "trunk", u""), 
 +            self.repos.generate_revision_id(1, "trunk", self.mapping)), 
 +          "dir": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"dir"), 
 +                self.repos.generate_revision_id(2, "trunk", self.mapping)),
 +          "dir/file": (self.mapping.generate_file_id(self.repos.uuid, 2, "trunk", u"dir/file"), 
 +              self.repos.generate_revision_id(2, "trunk", self.mapping)),
 +          "bar": (self.mapping.generate_file_id(self.repos.uuid, 3, "trunk", u"bar"), 
 +              self.repos.generate_revision_id(3, "trunk", self.mapping)),
 +          "bar/file": (self.mapping.generate_file_id(self.repos.uuid, 3, "trunk", u"bar/file"), 
 +              self.repos.generate_revision_id(3, "trunk", self.mapping))},
 +            self.repos.get_fileid_map(3, "trunk", self.mapping))
index e29a94832f04b02d17f7ba8c10aa9f335fe45a02,0000000000000000000000000000000000000000..6aa46571a0b1f80355535e4628ceaea8e033a01a
mode 100644,000000..100644
--- /dev/null
@@@ -1,331 -1,0 +1,331 @@@
-         (uuid, path, revnum, mapping) = self.mapping.parse_revision_id(revid)
 +# -*- coding: utf-8 -*-
 +
 +# Copyright (C) 2005-2008 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/>.
 +
 +import sha
 +
 +from bzrlib.errors import InvalidRevisionId
 +from bzrlib.tests import TestCase, TestNotApplicable
 +from bzrlib.revision import Revision
 +
 +from bzrlib.plugins.svn.errors import InvalidPropertyValue
 +from bzrlib.plugins.svn.mapping import (generate_revision_metadata, parse_revision_metadata, 
 +                     parse_revid_property, parse_merge_property, parse_text_parents_property,
 +                     generate_text_parents_property, BzrSvnMappingv1, BzrSvnMappingv2, 
 +                     parse_revision_id)
 +from bzrlib.plugins.svn.mapping3 import (BzrSvnMappingv3FileProps, BzrSvnMappingv3RevProps)
 +from bzrlib.plugins.svn.mapping4 import BzrSvnMappingv4
 +from bzrlib.plugins.svn.mapping3.scheme import NoBranchingScheme
 +
 +
 +class MetadataMarshallerTests(TestCase):
 +    def test_generate_revision_metadata_none(self):
 +        self.assertEquals("", 
 +                generate_revision_metadata(None, None, None, None))
 +
 +    def test_generate_revision_metadata_committer(self):
 +        self.assertEquals("committer: bla\n", 
 +                generate_revision_metadata(None, None, "bla", None))
 +
 +    def test_generate_revision_metadata_timestamp(self):
 +        self.assertEquals("timestamp: 2005-06-30 17:38:52.350850105 +0000\n", 
 +                generate_revision_metadata(1120153132.350850105, 0, 
 +                    None, None))
 +            
 +    def test_generate_revision_metadata_properties(self):
 +        self.assertEquals("properties: \n" + 
 +                "\tpropbla: bloe\n" +
 +                "\tpropfoo: bla\n",
 +                generate_revision_metadata(None, None,
 +                    None, {"propbla": "bloe", "propfoo": "bla"}))
 +
 +    def test_parse_revision_metadata_empty(self):
 +        parse_revision_metadata("", None)
 +
 +    def test_parse_revision_metadata_committer(self):
 +        rev = Revision('someid')
 +        parse_revision_metadata("committer: somebody\n", rev)
 +        self.assertEquals("somebody", rev.committer)
 +
 +    def test_parse_revision_metadata_timestamp(self):
 +        rev = Revision('someid')
 +        parse_revision_metadata("timestamp: 2005-06-30 12:38:52.350850105 -0500\n", rev)
 +        self.assertEquals(1120153132.3508501, rev.timestamp)
 +        self.assertEquals(-18000, rev.timezone)
 +
 +    def test_parse_revision_metadata_timestamp_day(self):
 +        rev = Revision('someid')
 +        parse_revision_metadata("timestamp: Thu 2005-06-30 12:38:52.350850105 -0500\n", rev)
 +        self.assertEquals(1120153132.3508501, rev.timestamp)
 +        self.assertEquals(-18000, rev.timezone)
 +
 +    def test_parse_revision_metadata_properties(self):
 +        rev = Revision('someid')
 +        parse_revision_metadata("properties: \n" + 
 +                                "\tfoo: bar\n" + 
 +                                "\tha: ha\n", rev)
 +        self.assertEquals({"foo": "bar", "ha": "ha"}, rev.properties)
 +
 +    def test_parse_revision_metadata_no_colon(self):
 +        rev = Revision('someid')
 +        self.assertRaises(InvalidPropertyValue, 
 +                lambda: parse_revision_metadata("bla", rev))
 +
 +    def test_parse_revision_metadata_specialchar(self):
 +        rev = Revision('someid')
 +        parse_revision_metadata("committer: Adeodato Simó <dato@net.com.org.es>", rev)
 +        self.assertEquals(u"Adeodato Simó <dato@net.com.org.es>", rev.committer)
 +
 +    def test_parse_revision_metadata_invalid_name(self):
 +        rev = Revision('someid')
 +        self.assertRaises(InvalidPropertyValue, 
 +                lambda: parse_revision_metadata("bla: b", rev))
 +
 +    def test_parse_revid_property(self):
 +        self.assertEquals((1, "bloe"), parse_revid_property("1 bloe"))
 +
 +    def test_parse_revid_property_space(self):
 +        self.assertEquals((42, "bloe bla"), parse_revid_property("42 bloe bla"))
 +
 +    def test_parse_revid_property_invalid(self):
 +        self.assertRaises(InvalidPropertyValue, 
 +                lambda: parse_revid_property("blabla"))
 +
 +    def test_parse_revid_property_empty_revid(self):
 +        self.assertRaises(InvalidPropertyValue, 
 +                lambda: parse_revid_property("2 "))
 +
 +    def test_parse_revid_property_newline(self):
 +        self.assertRaises(InvalidPropertyValue, 
 +                lambda: parse_revid_property("foo\nbar"))
 +
 +
 +class ParseTextParentsTestCase(TestCase):
 +    def test_text_parents(self):
 +        self.assertEquals({"bla": "bloe"}, parse_text_parents_property("bla\tbloe\n"))
 +
 +    def test_text_parents_empty(self):
 +        self.assertEquals({}, parse_text_parents_property(""))
 +
 +
 +class GenerateTextParentsTestCase(TestCase):
 +    def test_generate_empty(self):
 +        self.assertEquals("", generate_text_parents_property({}))
 +
 +    def test_generate_simple(self):
 +        self.assertEquals("bla\tbloe\n", generate_text_parents_property({"bla": "bloe"}))
 +
 +
 +class ParseMergePropertyTestCase(TestCase):
 +    def test_parse_merge_space(self):
 +        self.assertEqual((), parse_merge_property("bla bla"))
 +
 +    def test_parse_merge_empty(self):
 +        self.assertEqual((), parse_merge_property(""))
 +
 +    def test_parse_merge_simple(self):
 +        self.assertEqual(("bla", "bloe"), parse_merge_property("bla\tbloe"))
 +
 +
 +class MappingTestAdapter(object):
 +    def test_roundtrip_revision(self):
 +        revid = self.mapping.generate_revision_id(("myuuid", 42, "path"))
-                 lambda: self.mapping.parse_revision_id("bla"))
++        (uuid, path, revnum, mapping) = self.mapping.revision_id_foreign_to_bzr(revid)
 +        self.assertEquals(uuid, "myuuid")
 +        self.assertEquals(revnum, 42)
 +        self.assertEquals(path, "path")
 +        self.assertEquals(mapping, self.mapping)
 +
 +    def test_fileid_map(self):
 +        if not self.mapping.supports_roundtripping():
 +            raise TestNotApplicable
 +        fileids = {"": "some-id", "bla/blie": "other-id"}
 +        (revprops, fileprops) = self.mapping.export_revision(True, "branchp", 432432432.0, 0, "somebody", {}, "arevid", 4, ["merge1"], dict())
 +        self.mapping.export_fileid_map(True, fileids, revprops, fileprops)
 +        revprops["svn:date"] = "2008-11-03T09:33:00.716938Z"
 +        self.assertEquals(fileids, 
 +                self.mapping.import_fileid_map(revprops, fileprops))
 +
 +    def test_text_parents(self):
 +        if not self.mapping.supports_roundtripping():
 +            raise TestNotApplicable
 +        revprops = {}
 +        fileprops = {}
 +        text_parents = {"bla": "bloe", "ll": "12"}
 +        self.mapping.export_text_parents(True, text_parents, revprops, fileprops)
 +        self.assertEquals(text_parents,
 +            self.mapping.import_text_parents(revprops, fileprops))
 +
 +    def test_message(self):
 +        if not self.mapping.supports_roundtripping():
 +            raise TestNotApplicable
 +        (revprops, fileprops) = self.mapping.export_revision(True, "branchp", 432432432.0, 0, "somebody", 
 +                                     {"arevprop": "val"}, "arevid", 4, ["merge1"], dict())
 +        revprops["svn:date"] = "2008-11-03T09:33:00.716938Z"
 +        try:
 +            self.mapping.export_message(True, "My Commit message", revprops, fileprops)
 +        except NotImplementedError:
 +            raise TestNotApplicable
 +        targetrev = Revision(None)
 +        self.mapping.import_revision(revprops, fileprops, "someuuid", "somebp", 4, targetrev)
 +        self.assertEquals("My Commit message", targetrev.message)
 +
 +    def test_revision(self):
 +        if not self.mapping.supports_roundtripping():
 +            raise TestNotApplicable
 +        (revprops, fileprops) = self.mapping.export_revision(True, "branchp", 432432432.0, 0, "somebody", 
 +                                     {"arevprop": "val" }, "arevid", 4, ["merge1"], dict())
 +        targetrev = Revision(None)
 +        revprops["svn:date"] = "2008-11-03T09:33:00.716938Z"
 +        self.mapping.import_revision(revprops, fileprops, "someuuid", "somebp", 4, targetrev)
 +        self.assertEquals(targetrev.committer, "somebody")
 +        self.assertEquals(targetrev.properties, {"arevprop": "val"})
 +        self.assertEquals(targetrev.timestamp, 432432432.0)
 +        self.assertEquals(targetrev.timezone, 0)
 +
 +    def test_revision_id(self):
 +        if not self.mapping.supports_roundtripping():
 +            raise TestNotApplicable
 +        (revprops, fileprops) = self.mapping.export_revision(True, "branchp", 432432432.0, 0, "somebody", {}, "arevid", 4, ["merge1"], dict())
 +        self.assertEquals((4, "arevid"), self.mapping.get_revision_id("branchp", revprops, fileprops))
 +    
 +    def test_revision_id_none(self):
 +        if not self.mapping.supports_roundtripping():
 +            raise TestNotApplicable
 +        self.assertEquals((None, None), self.mapping.get_revision_id("bp", {}, dict()))
 +
 +    def test_parse_revision_id_unknown(self):
 +        self.assertRaises(InvalidRevisionId, 
-             self.mapping.parse_revision_id(
-                 self.mapping.generate_revision_id(("myuuid", 5, "bla"))))
++                lambda: self.mapping.revision_id_bzr_to_foreign("bla"))
 +
 +    def test_parse_revision_id(self):
 +        self.assertEquals(("myuuid", "bla", 5, self.mapping), 
++            self.mapping.revision_id_bzr_to_foreign(
++                self.mapping.revision_id_foreign_to_bzr(("myuuid", 5, "bla"))))
 +
 +
 +class Mappingv1Tests(MappingTestAdapter, TestCase):
 +    def setUp(self):
 +        self.mapping = BzrSvnMappingv1()
 +
 +
 +class Mappingv2Tests(MappingTestAdapter, TestCase):
 +    def setUp(self):
 +        self.mapping = BzrSvnMappingv2()
 +
 +
 +def sha1(text):
 +    return sha.new(text).hexdigest()
 +
 +
 +class Mappingv3FilePropTests(MappingTestAdapter, TestCase):
 +    def setUp(self):
 +        self.mapping = BzrSvnMappingv3FileProps(NoBranchingScheme())
 +
 +    def test_generate_revid(self):
 +        self.assertEqual("svn-v3-undefined:myuuid:branch:5", 
 +                         BzrSvnMappingv3FileProps._generate_revision_id("myuuid", 5, "branch", "undefined"))
 +
 +    def test_generate_revid_nested(self):
 +        self.assertEqual("svn-v3-undefined:myuuid:branch%2Fpath:5", 
 +                  BzrSvnMappingv3FileProps._generate_revision_id("myuuid", 5, "branch/path", "undefined"))
 +
 +    def test_generate_revid_special_char(self):
 +        self.assertEqual("svn-v3-undefined:myuuid:branch%2C:5", 
 +             BzrSvnMappingv3FileProps._generate_revision_id("myuuid", 5, "branch\x2c", "undefined"))
 +
 +    def test_generate_revid_nordic(self):
 +        self.assertEqual("svn-v3-undefined:myuuid:branch%C3%A6:5", 
 +             BzrSvnMappingv3FileProps._generate_revision_id("myuuid", 5, u"branch\xe6".encode("utf-8"), "undefined"))
 +
 +    def test_parse_revid_simple(self):
 +        self.assertEqual(("uuid", "", 4, "undefined"),
 +                         BzrSvnMappingv3FileProps._parse_revision_id(
 +                             "svn-v3-undefined:uuid::4"))
 +
 +    def test_parse_revid_nested(self):
 +        self.assertEqual(("uuid", "bp/data", 4, "undefined"),
 +                         BzrSvnMappingv3FileProps._parse_revision_id(
 +                     "svn-v3-undefined:uuid:bp%2Fdata:4"))
 +
 +    def test_generate_file_id_root(self):
 +        self.assertEqual("2@uuid:bp:", self.mapping.generate_file_id("uuid", 2, "bp", u""))
 +
 +    def test_generate_file_id_path(self):
 +        self.assertEqual("2@uuid:bp:mypath", 
 +                self.mapping.generate_file_id("uuid", 2, "bp", u"mypath"))
 +
 +    def test_generate_file_id_long(self):
 +        dir = "this/is/a" + ("/very"*40) + "/long/path/"
 +        self.assertEqual("2@uuid:bp;" + sha1(dir+"filename"), 
 +                self.mapping.generate_file_id("uuid", 2, "bp", dir+u"filename"))
 +
 +    def test_generate_file_id_long_nordic(self):
 +        dir = "this/is/a" + ("/very"*40) + "/long/path/"
 +        self.assertEqual("2@uuid:bp;" + sha1((dir+u"filename\x2c\x8a").encode('utf-8')), 
 +                self.mapping.generate_file_id("uuid", 2, "bp", dir+u"filename\x2c\x8a"))
 +
 +    def test_generate_file_id_special_char(self):
 +        self.assertEqual("2@uuid:bp:mypath%2C%C2%8A",
 +                         self.mapping.generate_file_id("uuid", 2, "bp", u"mypath\x2c\x8a"))
 +
 +    def test_generate_svn_file_id(self):
 +        self.assertEqual("2@uuid:bp:path", 
 +                self.mapping.generate_file_id("uuid", 2, "bp", u"path"))
 +
 +    def test_generate_svn_file_id_nordic(self):
 +        self.assertEqual("2@uuid:bp:%C3%A6%C3%B8%C3%A5", 
 +                self.mapping.generate_file_id("uuid", 2, "bp", u"\xe6\xf8\xe5"))
 +
 +    def test_generate_svn_file_id_nordic_branch(self):
 +        self.assertEqual("2@uuid:%C3%A6:%C3%A6%C3%B8%C3%A5", 
 +                self.mapping.generate_file_id("uuid", 2, u"\xe6".encode('utf-8'), u"\xe6\xf8\xe5"))
 +
 +
 +class Mappingv3RevPropTests(MappingTestAdapter, TestCase):
 +    def setUp(self):
 +        self.mapping = BzrSvnMappingv3RevProps(NoBranchingScheme())
 +
 +
 +class Mappingv4TestAdapter(MappingTestAdapter, TestCase):
 +    def setUp(self):
 +        self.mapping = BzrSvnMappingv4()
 +
 +
 +class ParseRevisionIdTests(object):
 +    def test_current(self):
 +        self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv3FileProps(TrunkBranchingScheme())), 
 +                parse_revision_id("svn-v3-trunk0:uuid:trunk:1"))
 +
 +    def test_current_undefined(self):
 +        self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv3FileProps(TrunkBranchingScheme())), 
 +                parse_revision_id("svn-v3-undefined:uuid:trunk:1"))
 +
 +    def test_legacy2(self):
 +        self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv2()), 
 +                         parse_revision_id("svn-v2:1@uuid-trunk"))
 +
 +    def test_legacy(self):
 +        self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv1()), 
 +                         parse_revision_id("svn-v1:1@uuid-trunk"))
 +
 +    def test_except(self):
 +        self.assertRaises(InvalidRevisionId, 
 +                         parse_revision_id, "svn-v0:1@uuid-trunk")
 +
 +    def test_except_nonsvn(self):
 +        self.assertRaises(InvalidRevisionId, 
 +                         parse_revision_id, "blah")
diff --cc upgrade.py
index 2977778e79d853143281bff1aca941159ba5a234,0000000000000000000000000000000000000000..4eb6e999d31987e56a395a8f71ab849ffd084ff3
mode 100644,000000..100644
--- /dev/null
@@@ -1,210 -1,0 +1,210 @@@
-         newrevid = new_mapping.generate_revision_id((uuid, rev, bp))
 +# Copyright (C) 2006 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.errors import BzrError, InvalidRevisionId
 +from bzrlib.trace import info
 +
 +import itertools
 +from bzrlib.plugins.svn.mapping import parse_revision_id
 +
 +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 upgrade_workingtree(wt, svn_repository, allow_changes=False, verbose=False):
 +    """Upgrade a working tree.
 +
 +    :param svn_repository: Subversion repository object
 +    """
 +    renames = upgrade_branch(wt.branch, svn_repository, allow_changes=allow_changes, verbose=verbose)
 +    last_revid = wt.branch.last_revision()
 +    wt.set_parent_trees([(last_revid, wt.branch.repository.revision_tree(last_revid))])
 +    # TODO: Should also adjust file ids in working tree if necessary
 +    return renames
 +
 +
 +def upgrade_branch(branch, svn_repository, 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, 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) = parse_revision_id(revid)
 +        except InvalidRevisionId:
 +            # Not a bzr-svn revision, nothing to do
 +            continue
-             return new_mapping.generate_revision_id((uuid, rev, bp))
++        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) = 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()