Use consistent order in svn revid tuples.
authorJelmer Vernooij <jelmer@samba.org>
Tue, 9 Sep 2008 13:36:52 +0000 (15:36 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Tue, 9 Sep 2008 13:36:52 +0000 (15:36 +0200)
19 files changed:
1  2 
commit.py
foreign/upgrade.py
log.py
mapping.py
mapping2.py
mapping3/__init__.py
mapping4.py
revids.py
revmeta.py
tests/mapping3/__init__.py
tests/mapping_implementations/test_base.py
tests/mapping_implementations/test_branch.py
tests/mapping_implementations/test_repository.py
tests/test_branch.py
tests/test_convert.py
tests/test_mapping.py
tests/test_tree.py
tests/test_upgrade.py
tests/test_workingtree.py

diff --cc commit.py
index c19a98ff252a4eb280f97fe2754a5d02dd924921,0000000000000000000000000000000000000000..0fb4e99f8b2e986b6761e7a64daddbe64842bffd
mode 100644,000000..100644
--- /dev/null
+++ b/commit.py
@@@ -1,1050 -1,0 +1,1050 @@@
-     (uuid, branch, revnum, _) = mapping_registry.parse_revision_id(revid)
 +# Copyright (C) 2006-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, write to the Free Software
 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +"""Committing and pushing to Subversion repositories."""
 +
 +from bzrlib import debug, urlutils, ui
 +from bzrlib.branch import Branch
 +from bzrlib.errors import (BzrError, InvalidRevisionId, DivergedBranches, 
 +                           UnrelatedBranches, AppendRevisionsOnlyViolation,
 +                           NoSuchRevision)
 +from bzrlib.inventory import Inventory
 +from bzrlib.repository import RootCommitBuilder, InterRepository, Repository
 +from bzrlib.revision import NULL_REVISION, ensure_null
 +from bzrlib.trace import mutter, warning
 +
 +from cStringIO import StringIO
 +
 +from bzrlib.plugins.svn import core, mapping, properties
 +from bzrlib.plugins.svn.core import SubversionException
 +from bzrlib.plugins.svn.delta import send_stream
 +from bzrlib.plugins.svn.errors import ChangesRootLHSHistory, MissingPrefix, RevpropChangeFailed, ERR_FS_TXN_OUT_OF_DATE, convert_svn_error
 +from bzrlib.plugins.svn.svk import (
 +    generate_svk_feature, serialize_svk_features, 
 +    parse_svk_features, SVN_PROP_SVK_MERGE)
 +from bzrlib.plugins.svn.logwalker import lazy_dict
 +from bzrlib.plugins.svn.mapping import mapping_registry
 +from bzrlib.plugins.svn.repository import SvnRepositoryFormat, SvnRepository
 +from bzrlib.plugins.svn.versionedfiles import SvnTexts
 +
 +def _revision_id_to_svk_feature(revid):
 +    """Create a SVK feature identifier from a revision id.
 +
 +    :param revid: Revision id to convert.
 +    :return: Matching SVK feature identifier.
 +    """
 +    assert isinstance(revid, str)
++    (uuid, branch, revnum), _ = mapping_registry.parse_revision_id(revid)
 +    # TODO: What about renamed revisions? Should use 
 +    # repository.lookup_revision_id here.
 +    return generate_svk_feature(uuid, branch, revnum)
 +
 +
 +def _check_dirs_exist(transport, bp_parts, base_rev):
 +    """Make sure that the specified directories exist.
 +
 +    :param transport: SvnRaTransport to use.
 +    :param bp_parts: List of directory names in the format returned by 
 +        os.path.split()
 +    :param base_rev: Base revision to check.
 +    :return: List of the directories that exists in base_rev.
 +    """
 +    for i in range(len(bp_parts), 0, -1):
 +        current = bp_parts[:i]
 +        path = "/".join(current).strip("/")
 +        assert isinstance(path, str)
 +        if transport.check_path(path, base_rev) == core.NODE_DIR:
 +            return current
 +    return []
 +
 +
 +def update_svk_features(oldvalue, merges):
 +    """Update a set of SVK features to include the specified set of merges."""
 +    old_svk_features = parse_svk_features(oldvalue)
 +    svk_features = set(old_svk_features)
 +
 +    # SVK compatibility
 +    for merge in merges:
 +        try:
 +            svk_features.add(_revision_id_to_svk_feature(merge))
 +        except InvalidRevisionId:
 +            pass
 +
 +    if old_svk_features != svk_features:
 +        return serialize_svk_features(svk_features)
 +    return None
 +
 +
 +def update_mergeinfo(repository, graph, oldvalue, baserevid, merges):
 +    """Update a svn:mergeinfo property to include a specified list of merges."""
 +    pb = ui.ui_factory.nested_progress_bar()
 +    try:
 +        mergeinfo = properties.parse_mergeinfo_property(oldvalue)
 +        for i, merge in enumerate(merges):
 +            pb.update("updating mergeinfo property", i, len(merges))
 +            for (revid, parents) in graph.iter_ancestry([merge]):
 +                if graph.is_ancestor(revid, baserevid):
 +                    break
 +                try:
 +                    (path, revnum, mapping) = repository.lookup_revision_id(revid)
 +                except NoSuchRevision:
 +                    break
 +
 +                properties.mergeinfo_add_revision(mergeinfo, "/" + path, revnum)
 +    finally:
 +        pb.finished()
 +    newvalue = properties.generate_mergeinfo_property(mergeinfo)
 +    if newvalue != oldvalue:
 +        return newvalue
 +    return None
 +
 +
 +def set_svn_revprops(transport, revnum, revprops):
 +    """Attempt to change the revision properties on the
 +    specified revision.
 +
 +    :param transport: SvnRaTransport connected to target repository
 +    :param revnum: Revision number of revision to change metadata of.
 +    :param revprops: Dictionary with revision properties to set.
 +    """
 +    for (name, value) in revprops.items():
 +        try:
 +            transport.change_rev_prop(revnum, name, value)
 +        except SubversionException, (_, ERR_REPOS_DISABLED_FEATURE):
 +            raise RevpropChangeFailed(name)
 +
 +
 +class SvnCommitBuilder(RootCommitBuilder):
 +    """Commit Builder implementation wrapped around svn_delta_editor. """
 +
 +    def __init__(self, repository, branch_path, parents, config, timestamp, 
 +                 timezone, committer, revprops, revision_id, old_inv=None,
 +                 push_metadata=True, graph=None, opt_signature=None,
 +                 texts=None, append_revisions_only=True):
 +        """Instantiate a new SvnCommitBuilder.
 +
 +        :param repository: SvnRepository to commit to.
 +        :param branch: branch path to commit to.
 +        :param parents: List of parent revision ids.
 +        :param config: Branch configuration to use.
 +        :param timestamp: Optional timestamp recorded for commit.
 +        :param timezone: Optional timezone for timestamp.
 +        :param committer: Optional committer to set for commit.
 +        :param revprops: Bazaar revision properties to set.
 +        :param revision_id: Revision id for the new revision.
 +        :param old_inv: Optional revision on top of which 
 +            the commit is happening
 +        :param push_metadata: Whether or not to push all bazaar metadata
 +                              (in svn file properties, etc).
 +        :param graph: Optional graph object
 +        :param opt_signature: Optional signature to write.
 +        """
 +        super(SvnCommitBuilder, self).__init__(repository, parents, 
 +            config, timestamp, timezone, committer, revprops, revision_id)
 +        self.branch_path = branch_path
 +        self.push_metadata = push_metadata
 +        self._append_revisions_only = append_revisions_only
 +        self._text_parents = {}
 +        self._texts = texts
 +
 +        # Gather information about revision on top of which the commit is 
 +        # happening
 +        if parents == []:
 +            self.base_revid = NULL_REVISION
 +        else:
 +            self.base_revid = parents[0]
 +
 +        if graph is None:
 +            graph = self.repository.get_graph()
 +        self.base_revno = graph.find_distance_to_null(self.base_revid, [])
 +        if self.base_revid == NULL_REVISION:
 +            self._base_revmeta = None
 +            self._base_branch_props = {}
 +            self.base_revnum = -1
 +            self.base_path = None
 +            self.base_mapping = repository.get_mapping()
 +        else:
 +            (self.base_path, self.base_revnum, self.base_mapping) = \
 +                repository.lookup_revision_id(self.base_revid)
 +            self._base_revmeta = self.repository._revmeta_provider.get_revision(self.base_path, self.base_revnum)
 +            self._base_branch_props = self._base_revmeta.get_fileprops()
 +
 +        if self.base_revid == NULL_REVISION:
 +            self.old_inv = Inventory(root_id=None)
 +        elif old_inv is None:
 +            self.old_inv = self.repository.get_inventory(self.base_revid)
 +        else:
 +            self.old_inv = old_inv
 +
 +        # Not all repositories appear to set Inventory.revision_id, 
 +        # so allow None as well.
 +        assert self.old_inv.revision_id in (None, self.base_revid), \
 +                "%s != %s" % (self.old_inv.revision_id, self.base_revid)
 +
 +        # Determine revisions merged in this one
 +        merges = filter(lambda x: x != self.base_revid, parents)
 +
 +        self.visit_dirs = set()
 +        self.modified_files = {}
 +        self.supports_custom_revprops = self.repository.transport.has_capability("commit-revprops")
 +        if (self.supports_custom_revprops is None and 
 +            self.base_mapping.can_use_revprops and 
 +            self.repository.seen_bzr_revprops()):
 +            raise BzrError("Please upgrade your Subversion client libraries to 1.5 or higher to be able to commit with Subversion mapping %s" % self.base_mapping.name)
 +
 +        if self.supports_custom_revprops == True:
 +            self._svn_revprops = {}
 +            # If possible, submit signature directly
 +            if opt_signature is not None:
 +                self._svn_revprops[mapping.SVN_REVPROP_BZR_SIGNATURE] = opt_signature
 +        else:
 +            self._svn_revprops = None
 +        self._svnprops = lazy_dict({}, lambda: dict(self._base_branch_props.items()))
 +        self.base_mapping.export_revision(
 +            self.branch_path, timestamp, timezone, committer, revprops, 
 +            revision_id, self.base_revno+1, parents, self._svn_revprops, self._svnprops)
 +
 +        if len(merges) > 0:
 +            new_svk_merges = update_svk_features(self._base_branch_props.get(SVN_PROP_SVK_MERGE, ""), merges)
 +            if new_svk_merges is not None:
 +                self._svnprops[SVN_PROP_SVK_MERGE] = new_svk_merges
 +
 +            new_mergeinfo = update_mergeinfo(self.repository, graph, self._base_branch_props.get(properties.PROP_MERGEINFO, ""), self.base_revid, merges)
 +            if new_mergeinfo is not None:
 +                self._svnprops[properties.PROP_MERGEINFO] = new_mergeinfo
 +
 +    @staticmethod
 +    def mutter(text, *args):
 +        if 'commit' in debug.debug_flags:
 +            mutter(text, *args)
 +
 +    def _generate_revision_if_needed(self):
 +        """See CommitBuilder._generate_revision_if_needed()."""
 +
 +    def finish_inventory(self):
 +        """See CommitBuilder.finish_inventory()."""
 +
 +    def _file_process(self, file_id, contents, file_editor):
 +        """Pass the changes to a file to the Subversion commit editor.
 +
 +        :param file_id: Id of the file to modify.
 +        :param contents: Contents of the file.
 +        :param file_editor: Subversion FileEditor object.
 +        """
 +        assert file_editor is not None
 +        txdelta = file_editor.apply_textdelta()
 +        digest = send_stream(StringIO(contents), txdelta)
 +        if 'validate' in debug.debug_flags:
 +            from fetch import md5_strings
 +            assert digest == md5_strings(contents)
 +
 +    def _dir_process(self, path, file_id, dir_editor):
 +        """Pass the changes to a directory to the commit editor.
 +
 +        :param path: Path (from repository root) to the directory.
 +        :param file_id: File id of the directory
 +        :param dir_editor: Subversion DirEditor object.
 +        """
 +        assert dir_editor is not None
 +        # Loop over entries of file_id in self.old_inv
 +        # remove if they no longer exist with the same name
 +        # or parents
 +        if file_id in self.old_inv:
 +            for child_name in self.old_inv[file_id].children:
 +                child_ie = self.old_inv.get_child(file_id, child_name)
 +                # remove if...
 +                if (
 +                    # ... path no longer exists
 +                    not child_ie.file_id in self.new_inventory or 
 +                    # ... parent changed
 +                    child_ie.parent_id != self.new_inventory[child_ie.file_id].parent_id or
 +                    # ... name changed
 +                    self.new_inventory[child_ie.file_id].name != child_name):
 +                    self.mutter('removing %r(%r)', (child_name, child_ie.file_id))
 +                    dir_editor.delete_entry(
 +                        urlutils.join(self.branch_path, path, child_name), 
 +                        self.base_revnum)
 +
 +        # Loop over file children of file_id in self.new_inventory
 +        for child_name in self.new_inventory[file_id].children:
 +            child_ie = self.new_inventory.get_child(file_id, child_name)
 +            assert child_ie is not None
 +
 +            if not (child_ie.kind in ('file', 'symlink')):
 +                continue
 +
 +            new_child_path = self.new_inventory.id2path(child_ie.file_id).encode("utf-8")
 +            full_new_child_path = urlutils.join(self.branch_path, 
 +                                  new_child_path)
 +            # add them if they didn't exist in old_inv 
 +            if not child_ie.file_id in self.old_inv:
 +                self.mutter('adding %s %r', child_ie.kind, new_child_path)
 +                child_editor = dir_editor.add_file(full_new_child_path)
 +
 +            # copy if they existed at different location
 +            elif (self.old_inv.id2path(child_ie.file_id) != new_child_path or
 +                    self.old_inv[child_ie.file_id].parent_id != child_ie.parent_id):
 +                self.mutter('copy %s %r -> %r', child_ie.kind, 
 +                                  self.old_inv.id2path(child_ie.file_id), 
 +                                  new_child_path)
 +                child_editor = dir_editor.add_file(
 +                        full_new_child_path, 
 +                    urlutils.join(self.repository.transport.svn_url, self.base_path, self.old_inv.id2path(child_ie.file_id)),
 +                    self.base_revnum)
 +
 +            # open if they existed at the same location
 +            elif child_ie.file_id in self.modified_files:
 +                self.mutter('open %s %r', child_ie.kind, new_child_path)
 +
 +                child_editor = dir_editor.open_file(
 +                        full_new_child_path, self.base_revnum)
 +
 +            else:
 +                # Old copy of the file was retained. No need to send changes
 +                child_editor = None
 +
 +            if child_ie.file_id in self.old_inv:
 +                old_executable = self.old_inv[child_ie.file_id].executable
 +                old_special = (self.old_inv[child_ie.file_id].kind == 'symlink')
 +            else:
 +                old_special = False
 +                old_executable = False
 +
 +            if child_editor is not None:
 +                if old_executable != child_ie.executable:
 +                    if child_ie.executable:
 +                        value = properties.PROP_EXECUTABLE_VALUE
 +                    else:
 +                        value = None
 +                    child_editor.change_prop(
 +                            properties.PROP_EXECUTABLE, value)
 +
 +                if old_special != (child_ie.kind == 'symlink'):
 +                    if child_ie.kind == 'symlink':
 +                        value = properties.PROP_SPECIAL_VALUE
 +                    else:
 +                        value = None
 +
 +                    child_editor.change_prop(
 +                            properties.PROP_SPECIAL, value)
 +
 +            # handle the file
 +            if child_ie.file_id in self.modified_files:
 +                self._file_process(child_ie.file_id, 
 +                    self.modified_files[child_ie.file_id], child_editor)
 +
 +            if child_editor is not None:
 +                child_editor.close()
 +
 +        # Loop over subdirectories of file_id in self.new_inventory
 +        for child_name in self.new_inventory[file_id].children:
 +            child_ie = self.new_inventory.get_child(file_id, child_name)
 +            if child_ie.kind != 'directory':
 +                continue
 +
 +            new_child_path = self.new_inventory.id2path(child_ie.file_id)
 +            # add them if they didn't exist in old_inv 
 +            if not child_ie.file_id in self.old_inv:
 +                self.mutter('adding dir %r', child_ie.name)
 +                child_editor = dir_editor.add_directory(
 +                    urlutils.join(self.branch_path, 
 +                                  new_child_path))
 +
 +            # copy if they existed at different location
 +            elif self.old_inv.id2path(child_ie.file_id) != new_child_path:
 +                old_child_path = self.old_inv.id2path(child_ie.file_id)
 +                self.mutter('copy dir %r -> %r', old_child_path, new_child_path)
 +                child_editor = dir_editor.add_directory(
 +                    urlutils.join(self.branch_path, new_child_path),
 +                    urlutils.join(self.repository.transport.svn_url, self.base_path, old_child_path), self.base_revnum)
 +
 +            # open if they existed at the same location and 
 +            # the directory was touched
 +            elif child_ie.file_id in self.visit_dirs:
 +                self.mutter('open dir %r', new_child_path)
 +
 +                child_editor = dir_editor.open_directory(
 +                        urlutils.join(self.branch_path, new_child_path), 
 +                        self.base_revnum)
 +            else:
 +                continue
 +
 +            # Handle this directory
 +            self._dir_process(new_child_path, child_ie.file_id, child_editor)
 +
 +            child_editor.close()
 +
 +    def open_branch_editors(self, root, elements, existing_elements, 
 +                           base_path, base_rev, replace_existing):
 +        """Open a specified directory given an editor for the repository root.
 +
 +        :param root: Editor for the repository root
 +        :param elements: List of directory names to open
 +        :param existing_elements: List of directory names that exist
 +        :param base_path: Path to base top-level branch on
 +        :param base_rev: Revision of path to base top-level branch on
 +        :param replace_existing: Whether the current branch should be replaced
 +        """
 +        ret = [root]
 +
 +        self.mutter('opening branch %r (base %r:%r)', elements, base_path, 
 +                                                   base_rev)
 +
 +        # Open paths leading up to branch
 +        for i in range(0, len(elements)-1):
 +            # Does directory already exist?
 +            ret.append(ret[-1].open_directory(
 +                "/".join(existing_elements[0:i+1]), -1))
 +
 +        if (len(existing_elements) != len(elements) and
 +            len(existing_elements)+1 != len(elements)):
 +            raise MissingPrefix("/".join(elements), "/".join(existing_elements))
 +
 +        # Branch already exists and stayed at the same location, open:
 +        # TODO: What if the branch didn't change but the new revision 
 +        # was based on an older revision of the branch?
 +        # This needs to also check that base_rev was the latest version of 
 +        # branch_path.
 +        if (len(existing_elements) == len(elements) and 
 +            not replace_existing):
 +            ret.append(ret[-1].open_directory(
 +                "/".join(elements), base_rev))
 +        else: # Branch has to be created
 +            # Already exists, old copy needs to be removed
 +            name = "/".join(elements)
 +            if replace_existing:
 +                if name == "":
 +                    raise ChangesRootLHSHistory()
 +                self.mutter("removing branch dir %r", name)
 +                ret[-1].delete_entry(name, -1)
 +            if base_path is not None:
 +                base_url = urlutils.join(self.repository.transport.svn_url, base_path)
 +            else:
 +                base_url = None
 +            self.mutter("adding branch dir %r", name)
 +            ret.append(ret[-1].add_directory(
 +                name, base_url, base_rev))
 +
 +        return ret
 +
 +    def _determine_texts_identity(self):
 +        # Store file ids
 +        def _dir_process_file_id(old_inv, new_inv, path, file_id):
 +            ret = []
 +            for child_name in new_inv[file_id].children:
 +                child_ie = new_inv.get_child(file_id, child_name)
 +                new_child_path = new_inv.id2path(child_ie.file_id)
 +                assert child_ie is not None
 +
 +                if (not child_ie.file_id in old_inv or 
 +                    old_inv.id2path(child_ie.file_id) != new_child_path or
 +                    old_inv[child_ie.file_id].revision != child_ie.revision or
 +                    old_inv[child_ie.file_id].parent_id != child_ie.parent_id):
 +                    ret.append((child_ie.file_id, new_child_path, child_ie.revision, self._text_parents[child_ie.file_id]))
 +
 +                if (child_ie.kind == 'directory' and 
 +                    child_ie.file_id in self.visit_dirs):
 +                    ret += _dir_process_file_id(old_inv, new_inv, new_child_path, child_ie.file_id)
 +            return ret
 +
 +        fileids = {}
 +        text_parents = {}
 +        text_revisions = {}
 +        changes = []
 +
 +        if (self.old_inv.root is None or 
 +            self.new_inventory.root.file_id != self.old_inv.root.file_id):
 +            changes.append((self.new_inventory.root.file_id, "", self.new_inventory.root.revision, self._text_parents[self.new_inventory.root.file_id]))
 +
 +        changes += _dir_process_file_id(self.old_inv, self.new_inventory, "", self.new_inventory.root.file_id)
 +
 +        for id, path, revid, parents in changes:
 +            fileids[path] = id
 +            if revid is not None and revid != self.base_revid and revid != self._new_revision_id:
 +                text_revisions[path] = revid
 +            if ((id not in self.old_inv and parents != []) or 
 +                (id in self.old_inv and parents != [self.base_revid])):
 +                text_parents[path] = parents
 +        return (fileids, text_revisions, text_parents)
 +
 +    def commit(self, message):
 +        """Finish the commit.
 +
 +        """
 +        def done(*args):
 +            """Callback that is called by the Subversion commit editor 
 +            once the commit finishes.
 +
 +            :param revision_data: Revision metadata
 +            """
 +            self.revision_metadata = args
 +        
 +        bp_parts = self.branch_path.split("/")
 +        repository_latest_revnum = self.repository.get_latest_revnum()
 +        lock = self.repository.transport.lock_write(".")
 +
 +        self._changed_fileprops = {}
 +
 +        if self.push_metadata:
 +            (fileids, text_revisions, text_parents) = self._determine_texts_identity()
 +
 +            self.base_mapping.export_text_revisions(text_revisions, self._svn_revprops, self._svnprops)
 +            self.base_mapping.export_text_parents(text_parents, self._svn_revprops, self._svnprops)
 +            self.base_mapping.export_fileid_map(fileids, self._svn_revprops, self._svnprops)
 +            if self._config.get_log_strip_trailing_newline():
 +                self.base_mapping.export_message(message, self._svn_revprops, self._svnprops)
 +                message = message.rstrip("\n")
 +        if not self.supports_custom_revprops:
 +            self._svn_revprops = {}
 +        self._svn_revprops[properties.PROP_REVISION_LOG] = message.encode("utf-8")
 +
 +        try:
 +            # Shortcut - no need to see if dir exists if our base 
 +            # was the last revision in the repo. This situation 
 +            # happens a lot when pushing multiple subsequent revisions.
 +            if (self.base_revnum == self.repository.get_latest_revnum() and 
 +                self.base_path == self.branch_path):
 +                existing_bp_parts = bp_parts
 +            else:
 +                existing_bp_parts = _check_dirs_exist(self.repository.transport, 
 +                                              bp_parts, -1)
 +            self.revision_metadata = None
 +            for prop in self._svn_revprops:
 +                assert prop.split(":")[0] in ("bzr", "svk", "svn")
 +                if not properties.is_valid_property_name(prop):
 +                    warning("Setting property %r with invalid characters in name", prop)
 +            conn = self.repository.transport.get_connection()
 +            assert self.supports_custom_revprops or self._svn_revprops.keys() == [properties.PROP_REVISION_LOG], \
 +                    "revprops: %r" % self._svn_revprops.keys()
 +            self.editor = convert_svn_error(conn.get_commit_editor)(
 +                    self._svn_revprops, done, None, False)
 +            try:
 +                root = self.editor.open_root(self.base_revnum)
 +
 +                replace_existing = False
 +                # See whether the base of the commit matches the lhs parent
 +                # if not, we need to replace the existing directory
 +                if len(bp_parts) == len(existing_bp_parts):
 +                    if self.base_path is None or self.base_path.strip("/") != "/".join(bp_parts).strip("/"):
 +                        replace_existing = True
 +                    elif self.base_revnum < self.repository._log.find_latest_change(self.branch_path, repository_latest_revnum):
 +                        replace_existing = True
 +
 +                if replace_existing and self._append_revisions_only:
 +                    raise AppendRevisionsOnlyViolation(urlutils.join(self.repository.base, self.branch_path))
 +
 +                # TODO: Accept create_prefix argument
 +                branch_editors = self.open_branch_editors(root, bp_parts,
 +                    existing_bp_parts, self.base_path, self.base_revnum, 
 +                    replace_existing)
 +
 +                self._dir_process("", self.new_inventory.root.file_id, 
 +                    branch_editors[-1])
 +
 +                # Set all the revprops
 +                if self.push_metadata and self._svnprops.is_loaded:
 +                    for prop, value in self._svnprops.items():
 +                        if value == self._base_branch_props.get(prop):
 +                            continue
 +                        self._changed_fileprops[prop] = value
 +                        if not properties.is_valid_property_name(prop):
 +                            warning("Setting property %r with invalid characters in name", prop)
 +                        assert isinstance(value, str)
 +                        branch_editors[-1].change_prop(prop, value)
 +                        self.mutter("Setting root file property %r -> %r", prop, value)
 +
 +                for dir_editor in reversed(branch_editors):
 +                    dir_editor.close()
 +            except:
 +                self.editor.abort()
 +                self.repository.transport.add_connection(conn)
 +                raise
 +
 +            self.editor.close()
 +            self.repository.transport.add_connection(conn)
 +        finally:
 +            lock.unlock()
 +
 +        (result_revision, result_date, result_author) = self.revision_metadata
 +        
 +        self._svn_revprops[properties.PROP_REVISION_AUTHOR] = result_author
 +        self._svn_revprops[properties.PROP_REVISION_DATE] = result_date
 +
 +        self.repository._clear_cached_state(result_revision)
 +
 +        self.mutter('commit %d finished. author: %r, date: %r',
 +               result_revision, result_author, 
 +                   result_date)
 +
 +        override_svn_revprops = self._config.get_override_svn_revprops()
 +        if override_svn_revprops is not None:
 +            new_revprops = {}
 +            if properties.PROP_REVISION_AUTHOR in override_svn_revprops:
 +                new_revprops[properties.PROP_REVISION_AUTHOR] = self._committer.encode("utf-8")
 +            if properties.PROP_REVISION_DATE in override_svn_revprops:
 +                new_revprops[properties.PROP_REVISION_DATE] = properties.time_to_cstring(1000000*self._timestamp)
 +            set_svn_revprops(self.repository.transport, result_revision, new_revprops)
 +            self._svn_revprops.update(new_revprops)
 +
 +        self.revmeta = self.repository._revmeta_provider.get_revision(self.branch_path, result_revision, 
 +                None, # FIXME: Generate changes dictionary
 +                revprops=self._svn_revprops,
 +                changed_fileprops=self._changed_fileprops,
 +                fileprops=self._svnprops,
 +                metabranch=None # FIXME: Determine from base_revmeta ?
 +                )
 +
 +        revid = self.revmeta.get_revision_id(self.base_mapping)
 +
 +        assert not self.push_metadata or self._new_revision_id is None or self._new_revision_id == revid
 +        return revid
 +
 +    def record_entry_contents(self, ie, parent_invs, path, tree,
 +                              content_summary):
 +        """Record the content of ie from tree into the commit if needed.
 +
 +        Side effect: sets ie.revision when unchanged
 +
 +        :param ie: An inventory entry present in the commit.
 +        :param parent_invs: The inventories of the parent revisions of the
 +            commit.
 +        :param path: The path the entry is at in the tree.
 +        :param tree: The tree which contains this entry and should be used to 
 +            obtain content.
 +        :param content_summary: Summary data from the tree about the paths
 +                content - stat, length, exec, sha/link target. This is only
 +                accessed when the entry has a revision of None - that is when 
 +                it is a candidate to commit.
 +        """
 +        if self._texts is None:
 +            self._text_parents[ie.file_id] = [parent_inv[ie.file_id].revision for parent_inv in parent_invs if ie.file_id in parent_inv]
 +        elif isinstance(self._texts, SvnTexts):
 +            overridden_parents = self._texts._get_parent(ie.file_id, ie.revision)
 +            if overridden_parents is None:
 +                if ie.file_id in self.old_inv:
 +                    self._text_parents[ie.file_id] = [self.old_inv[ie.file_id].revision]
 +                else:
 +                    self._text_parents[ie.file_id] = []
 +            else:
 +                self._text_parents[ie.file_id] = overridden_parents
 +        else:
 +            key = (ie.file_id, ie.revision)
 +            parent_map = self._texts.get_parent_map([key])
 +            if ie.parent_id is None and (not key in parent_map or parent_map[key] is None):
 +                # non-rich-root repositories don't have a text for the root
 +                self._text_parents[ie.file_id] = self.parents
 +            else:
 +                assert parent_map[key] is not None, "No parents found for %r" % (key,)
 +                self._text_parents[ie.file_id] = [r[1] for r in parent_map[key]]
 +        self.new_inventory.add(ie)
 +        assert (ie.file_id not in self.old_inv or 
 +                self.old_inv[ie.file_id].revision is not None)
 +        version_recorded = (ie.revision is None)
 +        # If nothing changed since the lhs parent, return:
 +        if (ie.file_id in self.old_inv and ie == self.old_inv[ie.file_id] and 
 +            (ie.kind != 'directory' or ie.children == self.old_inv[ie.file_id].children)):
 +            return self._get_delta(ie, self.old_inv, self.new_inventory.id2path(ie.file_id)), version_recorded
 +        if ie.kind == 'file':
 +            self.modified_files[ie.file_id] = tree.get_file_text(ie.file_id)
 +        elif ie.kind == 'symlink':
 +            self.modified_files[ie.file_id] = "link %s" % ie.symlink_target
 +        elif ie.kind == 'directory':
 +            self.visit_dirs.add(ie.file_id)
 +        fid = ie.parent_id
 +        while fid is not None and fid not in self.visit_dirs:
 +            self.visit_dirs.add(fid)
 +            fid = self.new_inventory[fid].parent_id
 +        return self._get_delta(ie, self.old_inv, self.new_inventory.id2path(ie.file_id)), version_recorded
 +
 +
 +def replay_delta(builder, old_trees, new_tree):
 +    """Replays a delta to a commit builder.
 +
 +    :param builder: The commit builder.
 +    :param old_tree: Original tree on top of which the delta should be applied
 +    :param new_tree: New tree that should be committed
 +    """
 +    for path, ie in new_tree.inventory.iter_entries():
 +        builder.record_entry_contents(ie.copy(), 
 +            [old_tree.inventory for old_tree in old_trees], 
 +            path, new_tree, None)
 +    builder.finish_inventory()
 +
 +
 +def create_branch_with_hidden_commit(repository, branch_path, revid, deletefirst=False):
 +    """Create a new branch using a simple "svn cp" operation.
 +
 +    :param repository: Repository in which to create the branch.
 +    :param branch_path: Branch path
 +    :param revid: Revision id to keep as tip.
 +    :param deletefirst: Whether to delete an existing branch at this location first.
 +    """
 +    revprops = {properties.PROP_REVISION_LOG: "Create new branch."}
 +    revmeta, mapping = repository._get_revmeta(revid)
 +    fileprops = dict(revmeta.get_fileprops().items())
 +    mapping.export_hidden(revprops, fileprops)
 +    parent = urlutils.dirname(branch_path)
 +
 +    bp_parts = branch_path.split("/")
 +    existing_bp_parts =_check_dirs_exist(repository.transport, bp_parts, -1)
 +    if (len(bp_parts) not in (len(existing_bp_parts), len(existing_bp_parts)+1)):
 +        raise MissingPrefix("/".join(bp_parts), "/".join(existing_bp_parts))
 +
 +    conn = repository.transport.get_connection(parent)
 +    try:
 +        ci = convert_svn_error(conn.get_commit_editor)(revprops)
 +        try:
 +            root = ci.open_root()
 +            if deletefirst:
 +                root.delete_entry(urlutils.basename(branch_path))
 +            branch_dir = root.add_directory(urlutils.basename(branch_path), urlutils.join(repository.base, revmeta.branch_path), revmeta.revnum)
 +            for k, v in properties.diff(fileprops, revmeta.get_fileprops()).items():
 +                branch_dir.change_prop(k, v)
 +            branch_dir.close()
 +            root.close()
 +        except:
 +            ci.abort()
 +            raise
 +        ci.close()
 +    finally:
 +        repository.transport.add_connection(conn)
 +
 +
 +def push_new(graph, target_repository, target_branch_path, source, stop_revision,
 +             push_metadata=True, append_revisions_only=False):
 +    """Push a revision into Subversion, creating a new branch.
 +
 +    This will do a new commit in the target branch.
 +
 +    :param graph: Repository graph.
 +    :param target_repository: Repository to push to
 +    :param target_branch_path: Path to create new branch at
 +    :param source: Source repository
 +    """
 +    assert isinstance(source, Repository)
 +    start_revid = stop_revision
 +    for revid in source.iter_reverse_revision_history(stop_revision):
 +        if target_repository.has_revision(revid):
 +            break
 +        start_revid = revid
 +    rev = source.get_revision(start_revid)
 +    if rev.parent_ids == []:
 +        start_revid_parent = NULL_REVISION
 +    else:
 +        start_revid_parent = rev.parent_ids[0]
 +    # If this is just intended to create a new branch
 +    mapping = target_repository.get_mapping()
 +    if (start_revid != NULL_REVISION and start_revid_parent != NULL_REVISION and stop_revision == start_revid and mapping.supports_hidden):
 +        if target_repository.has_revision(start_revid) or start_revid == NULL_REVISION:
 +            revid = start_revid
 +        else:
 +            revid = start_revid_parent
 +        create_branch_with_hidden_commit(target_repository, target_branch_path, revid, mapping)
 +    else:
 +        return push_revision_tree(graph, target_repository, target_branch_path, 
 +                              target_repository.get_config(), 
 +                              source, start_revid_parent, start_revid, 
 +                              rev, push_metadata=push_metadata,
 +                              append_revisions_only=append_revisions_only)
 +
 +
 +
 +def dpush(target, source, stop_revision=None):
 +    """Push derivatives of the revisions missing from target from source into 
 +    target.
 +
 +    :param target: Branch to push into
 +    :param source: Branch to retrieve revisions from
 +    :param stop_revision: If not None, stop at this revision.
 +    :return: Map of old revids to new revids.
 +    """
 +    source.lock_write()
 +    try:
 +        if stop_revision is None:
 +            stop_revision = ensure_null(source.last_revision())
 +        if target.last_revision() in (stop_revision, source.last_revision()):
 +            return {}
 +        graph = target.repository.get_graph()
 +        if not source.repository.get_graph().is_ancestor(target.last_revision(), 
 +                                                        stop_revision):
 +            if graph.is_ancestor(stop_revision, target.last_revision()):
 +                return {}
 +            raise DivergedBranches(source, target)
 +        todo = target.mainline_missing_revisions(source, stop_revision)
 +        revid_map = {}
 +        pb = ui.ui_factory.nested_progress_bar()
 +        try:
 +            for revid in todo:
 +                pb.update("pushing revisions", todo.index(revid), 
 +                          len(todo))
 +                revid_map[revid] = push(graph, target, source.repository, 
 +                                        revid, push_metadata=False)
 +                source.repository.fetch(target.repository, 
 +                                        revision_id=revid_map[revid])
 +                target._clear_cached_state()
 +        finally:
 +            pb.finished()
 +        return revid_map
 +    finally:
 +        source.unlock()
 +
 +
 +def push_revision_tree(graph, target_repo, branch_path, config, source_repo, base_revid, 
 +                       revision_id, rev, push_metadata=True,
 +                       append_revisions_only=True):
 +    """Push a revision tree into a target repository.
 +
 +    :param graph: Repository graph.
 +    :param target_repo: Target repository.
 +    :param branch_path: Branch path.
 +    :param config: Branch configuration.
 +    :param source_repo: Source repository.
 +    :param base_revid: Base revision id.
 +    :param revision_id: Revision id to push.
 +    :param rev: Revision object of revision to push.
 +    :param push_metadata: Whether to push metadata.
 +    :param append_revisions_only: Append revisions only.
 +    :return: Revision id of newly created revision.
 +    """
 +    assert rev.revision_id in (None, revision_id)
 +    old_tree = source_repo.revision_tree(revision_id)
 +    base_tree = source_repo.revision_tree(base_revid)
 +
 +    if push_metadata:
 +        base_revids = rev.parent_ids
 +    else:
 +        base_revids = [base_revid]
 +
 +    try:
 +        opt_signature = source_repo.get_signature_text(rev.revision_id)
 +    except NoSuchRevision:
 +        opt_signature = None
 +
 +    if base_revids == rev.parent_ids:
 +        parent_trees = [base_tree]
 +    else:
 +        parent_trees = source_repo.revision_trees(rev.parent_ids)
 +
 +    builder = SvnCommitBuilder(target_repo, branch_path, base_revids,
 +                               config, rev.timestamp,
 +                               rev.timezone, rev.committer, rev.properties, 
 +                               revision_id, 
 +                               base_tree.inventory,
 +                               push_metadata=push_metadata,
 +                               graph=graph, opt_signature=opt_signature,
 +                               texts=source_repo.texts,
 +                               append_revisions_only=append_revisions_only)
 +                         
 +    replay_delta(builder, parent_trees, old_tree)
 +    try:
 +        revid = builder.commit(rev.message)
 +    except SubversionException, (_, num):
 +        if num == ERR_FS_TXN_OUT_OF_DATE:
 +            raise DivergedBranches(source, target_repo)
 +        raise
 +    except ChangesRootLHSHistory:
 +        raise BzrError("Unable to push revision %r because it would change the ordering of existing revisions on the Subversion repository root. Use rebase and try again or push to a non-root path" % revision_id)
 +
 +    return revid
 +
 +
 +def push(graph, target, source_repo, revision_id, push_metadata=True):
 +    """Push a revision into Subversion.
 +
 +    This will do a new commit in the target branch.
 +
 +    :param target: Branch to push to
 +    :param source_repo: Branch to pull the revision from
 +    :param revision_id: Revision id of the revision to push
 +    :return: revision id of revision that was pushed
 +    """
 +    assert isinstance(source_repo, Repository)
 +    rev = source_repo.get_revision(revision_id)
 +    mutter('pushing %r (%r)', revision_id, rev.parent_ids)
 +
 +    # revision on top of which to commit
 +    if push_metadata:
 +        if rev.parent_ids == []:
 +            base_revid = NULL_REVISION
 +        else:
 +            base_revid = rev.parent_ids[0]
 +    else:
 +        base_revid = target.last_revision()
 +
 +    source_repo.lock_read()
 +    try:
 +        revid = push_revision_tree(graph, target.repository, target.get_branch_path(), target.get_config(), 
 +                                   source_repo, base_revid, revision_id, 
 +                                   rev, push_metadata=push_metadata,
 +                                   append_revisions_only=target._get_append_revisions_only())
 +    finally:
 +        source_repo.unlock()
 +
 +    assert revid == revision_id or not push_metadata
 +
 +    if 'validate' in debug.debug_flags and push_metadata:
 +        crev = target.repository.get_revision(revision_id)
 +        ctree = target.repository.revision_tree(revision_id)
 +        assert crev.committer == rev.committer
 +        assert crev.timezone == rev.timezone
 +        assert crev.timestamp == rev.timestamp
 +        assert crev.message == rev.message
 +        assert crev.properties == rev.properties
 +
 +    return revid
 +
 +
 +class InterToSvnRepository(InterRepository):
 +    """Any to Subversion repository actions."""
 +
 +    _matching_repo_format = SvnRepositoryFormat()
 +
 +    @staticmethod
 +    def _get_repo_format_to_test():
 +        """See InterRepository._get_repo_format_to_test()."""
 +        return None
 +
 +    def copy_content(self, revision_id=None, pb=None):
 +        """See InterRepository.copy_content."""
 +        self.source.lock_read()
 +        try:
 +            assert revision_id is not None, "fetching all revisions not supported"
 +            # Go back over the LHS parent until we reach a revid we know
 +            todo = []
 +            while not self.target.has_revision(revision_id):
 +                todo.append(revision_id)
 +                try:
 +                    revision_id = self.source.get_parent_map([revision_id])[revision_id][0]
 +                except KeyError:
 +                    # We hit a ghost
 +                    break
 +                if revision_id == NULL_REVISION:
 +                    raise UnrelatedBranches()
 +            if todo == []:
 +                # Nothing to do
 +                return
 +            mutter("pushing %r into svn", todo)
 +            target_branch = None
 +            layout = self.target.get_layout()
 +            graph = self.target.get_graph()
 +            for revision_id in todo:
 +                if pb is not None:
 +                    pb.update("pushing revisions", todo.index(revision_id), len(todo))
 +                rev = self.source.get_revision(revision_id)
 +
 +                mutter('pushing %r', revision_id)
 +
 +                parent_revid = rev.parent_ids[0]
 +
 +                (bp, _, _) = self.target.lookup_revision_id(parent_revid)
 +                if target_branch is None:
 +                    target_branch = Branch.open(urlutils.join(self.target.base, bp))
 +                if target_branch.get_branch_path() != bp:
 +                    target_branch.set_branch_path(bp)
 +
 +                target_config = target_branch.get_config()
 +                if (layout.push_merged_revisions(target_branch.project) and 
 +                    len(rev.parent_ids) > 1 and
 +                    target_config.get_push_merged_revisions()):
 +                    push_ancestors(self.target, self.source, layout, "", rev.parent_ids, graph,
 +                                   create_prefix=True)
 +
 +                push_revision_tree(graph, target_branch.repository, target_branch.get_branch_path(), 
 +                                   target_config, self.source, parent_revid, revision_id, rev,
 +                                   append_revisions_only=target_branch._get_append_revisions_only())
 +        finally:
 +            self.source.unlock()
 + 
 +
 +    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
 +        """Fetch revisions. """
 +        self.copy_content(revision_id=revision_id, pb=pb)
 +
 +    @staticmethod
 +    def is_compatible(source, target):
 +        """Be compatible with SvnRepository."""
 +        return isinstance(target, SvnRepository)
 +
 +
 +def push_ancestors(target_repo, source_repo, layout, project, parent_revids, graph, create_prefix=False):
 +    """Push the ancestors of a revision.
 +
 +    :param target_repo: Target repository.
 +    :param source_repo: Source repository
 +    :param layout: Subversion layout
 +    :param project: Project name
 +    :param parent_revids: The revision ids of the basic ancestors to push
 +    :param graph: Graph object for source_repo
 +    :param create_prefix: Whether to optionally create the prefix of the branches.
 +    """
 +    for parent_revid in parent_revids[1:]:
 +        if target_repo.has_revision(parent_revid):
 +            continue
 +        # Push merged revisions
 +        unique_ancestors = graph.find_unique_ancestors(parent_revid, [parent_revids[0]])
 +        for x in graph.iter_topo_order(unique_ancestors):
 +            if target_repo.has_revision(x):
 +                continue
 +            rev = source_repo.get_revision(x)
 +            nick = (rev.properties.get('branch-nick') or "merged").encode("utf-8").replace("/","_")
 +            rhs_branch_path = layout.get_branch_path(nick, project)
 +            try:
 +                push_new(graph, target_repo, rhs_branch_path, source_repo, x, append_revisions_only=False)
 +            except MissingPrefix, e:
 +                if not create_prefix:
 +                    raise
 +                revprops = {properties.PROP_REVISION_LOG: "Add branches directory."}
 +                if target_repo.transport.has_capability("commit-revprops"):
 +                    revprops[mapping.SVN_REVPROP_BZR_SKIP] = ""
 +                create_branch_prefix(target_repo, revprops, e.path.split("/")[:-1], filter(lambda x: x != "", e.existing_path.split("/")))
 +                push_new(graph, target_repo, rhs_branch_path, source_repo, x, append_revisions_only=False)
 +
 +
 +def create_branch_prefix(repository, revprops, bp_parts, existing_bp_parts):
 +    """Create a branch prefixes (e.g. "branches")
 +
 +    :param repository: Subversion repository
 +    :param revprops: Revision properties to set
 +    :param bp_parts: Branch path elements that should be created (list of names, 
 +        ["branches", "foo"] for "branches/foo")
 +    :param existing_bp_parts: Branch path elements that already exist.
 +    """
 +    conn = repository.transport.get_connection()
 +    try:
 +        ci = convert_svn_error(conn.get_commit_editor)(revprops)
 +        try:
 +            root = ci.open_root()
 +            name = None
 +            batons = [root]
 +            for p in existing_bp_parts:
 +                if name is None:
 +                    name = p
 +                else:
 +                    name += "/" + p
 +                batons.append(batons[-1].open_directory(name))
 +            for p in bp_parts[len(existing_bp_parts):]:
 +                if name is None:
 +                    name = p
 +                else:
 +                    name += "/" + p
 +                batons.append(batons[-1].add_directory(name))
 +            for baton in reversed(batons):
 +                baton.close()
 +        except:
 +            ci.abort()
 +            raise
 +        ci.close()
 +    finally:
 +        repository.transport.add_connection(conn)
index b7de0f1631c7a5925f8b827494fdcb2a00a07de2,0000000000000000000000000000000000000000..408b40acb99324e574b64c47b1ca279fa95df3ea
mode 100644,000000..100644
--- /dev/null
@@@ -1,256 -1,0 +1,257 @@@
- def upgrade_branch(branch, foreign_repository, new_mapping=None, 
-                    allow_changes=False, verbose=False):
 +# Copyright (C) 2006,2008 by Jelmer Vernooij
 +# 
 +# This program is free software; you can redistribute it and/or modify
 +# it under the terms of the GNU General Public License as published by
 +# the Free Software Foundation; either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU General Public License for more details.
 +#
 +# You should have received a copy of the GNU General Public License
 +# along with this program; if not, write to the Free Software
 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +"""Upgrading revisions made with older versions of the mapping."""
 +
 +from bzrlib import ui
 +from bzrlib.errors import BzrError, InvalidRevisionId, DependencyNotPresent
 +from bzrlib.revision import Revision
 +from bzrlib.trace import info
 +
 +import itertools
 +
 +class RebaseNotPresent(DependencyNotPresent):
 +    _fmt = "Unable to import bzr-rebase (required for upgrade support): %(error)s"
 +
 +    def __init__(self, error):
 +        DependencyNotPresent.__init__(self, 'bzr-rebase', error)
 +
 +
 +def check_rebase_version(min_version):
 +    """Check what version of bzr-rebase is installed.
 +
 +    Raises an exception when the version installed is older than 
 +    min_version.
 +
 +    :raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
 +    """
 +    try:
 +        from bzrlib.plugins.rebase import version_info as rebase_version_info
 +        if rebase_version_info[:2] < min_version:
 +            raise RebaseNotPresent("Version %r present, at least %r required" 
 +                                   % (rebase_version_info, min_version))
 +    except ImportError, e:
 +        raise RebaseNotPresent(e)
 +
 +
 +
 +class UpgradeChangesContent(BzrError):
 +    """Inconsistency was found upgrading the mapping of a revision."""
 +    _fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
 +
 +    def __init__(self, revid):
 +        self.revid = revid
 +
 +
 +
 +def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
 +    """Create a new revision id for an upgraded version of a revision.
 +    
 +    Prevents suffix to be appended needlessly.
 +
 +    :param revid: Original revision id.
 +    :return: New revision id
 +    """
 +    if revid.endswith(upgrade_suffix):
 +        return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
 +    else:
 +        return revid + mapping_suffix + upgrade_suffix
 +
 +
 +def determine_fileid_renames(old_tree, new_tree):
 +    for old_file_id in old_tree:
 +        new_file_id = new_tree.path2id(old_tree.id2path(old_file_id))
 +        if new_file_id is not None:
 +            yield old_file_id, new_file_id
 +
 +
 +def upgrade_workingtree(wt, foreign_repository, new_mapping, mapping_registry, 
 +                        allow_changes=False, verbose=False):
 +    """Upgrade a working tree.
 +
 +    :param foreign_repository: Foreign repository object
 +    """
 +    wt.lock_write()
 +    try:
 +        old_revid = wt.last_revision()
 +        renames = upgrade_branch(wt.branch, foreign_repository, new_mapping=new_mapping,
 +                                 mapping_registry=mapping_registry,
 +                                 allow_changes=allow_changes, verbose=verbose)
 +        last_revid = wt.branch.last_revision()
 +        wt.set_last_revision(last_revid)
 +
 +        # Adjust file ids in working tree
 +        for (old_fileid, new_fileid) in determine_fileid_renames(wt.branch.repository.revision_tree(old_revid), wt.basis_tree()):
 +            path = wt.id2path(old_fileid)
 +            wt.remove(path)
 +            wt.add([path], [new_fileid])
 +    finally:
 +        wt.unlock()
 +
 +    return renames
 +
 +
-             (foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
++def upgrade_branch(branch, foreign_repository, new_mapping, 
++                   mapping_registry, allow_changes=False, verbose=False):
 +    """Upgrade a branch to the current mapping version.
 +    
 +    :param branch: Branch to upgrade.
 +    :param foreign_repository: Repository to fetch new revisions from
 +    :param allow_changes: Allow changes in mappings.
 +    :param verbose: Whether to print verbose list of rewrites
 +    """
 +    revid = branch.last_revision()
 +    renames = upgrade_repository(branch.repository, foreign_repository, 
 +              revision_id=revid, new_mapping=new_mapping,
++              mapping_registry=mapping_registry,
 +              allow_changes=allow_changes, verbose=verbose)
 +    if len(renames) > 0:
 +        branch.generate_revision_history(renames[revid])
 +    return renames
 +
 +
 +def check_revision_changed(oldrev, newrev):
 +    """Check if two revisions are different. This is exactly the same 
 +    as Revision.equals() except that it does not check the revision_id."""
 +    if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
 +        newrev.timestamp != oldrev.timestamp or
 +        newrev.message != oldrev.message or
 +        newrev.timezone != oldrev.timezone or
 +        newrev.committer != oldrev.committer or
 +        newrev.properties != oldrev.properties):
 +        raise UpgradeChangesContent(oldrev.revision_id)
 +
 +
 +def generate_upgrade_map(new_mapping, revs, mapping_registry):
 +    """Generate an upgrade map for use by bzr-rebase.
 +
 +    :param new_mapping: Mapping to upgrade revisions to.
 +    :param revs: Iterator over revisions to upgrade.
 +    :return: Map from old revids as keys, new revids as values stored in a 
 +             dictionary.
 +    """
 +    rename_map = {}
 +    # Create a list of revisions that can be renamed during the upgade
 +    for revid in revs:
 +        assert isinstance(revid, str)
 +        try:
++            (foreign_revid, _) = mapping_registry.parse_revision_id(revid)
 +        except InvalidRevisionId:
 +            # Not a foreign revision, nothing to do
 +            continue
 +        newrevid = new_mapping.revision_id_foreign_to_bzr(foreign_revid)
 +        if revid == newrevid:
 +            continue
 +        rename_map[revid] = newrevid
 +
 +    return rename_map
 +
 +MIN_REBASE_VERSION = (0, 4)
 +
 +def create_upgrade_plan(repository, foreign_repository, new_mapping,
 +                        mapping_registry, revision_id=None, allow_changes=False):
 +    """Generate a rebase plan for upgrading revisions.
 +
 +    :param repository: Repository to do upgrade in
 +    :param foreign_repository: Subversion repository to fetch new revisions from.
 +    :param new_mapping: New mapping to use.
 +    :param revision_id: Revision to upgrade (None for all revisions in 
 +        repository.)
 +    :param allow_changes: Whether an upgrade is allowed to change the contents
 +        of revisions.
 +    :return: Tuple with a rebase plan and map of renamed revisions.
 +    """
 +    from bzrlib.plugins.rebase.rebase import generate_transpose_plan
 +    check_rebase_version(MIN_REBASE_VERSION)
 +
 +    graph = repository.get_graph()
 +    if revision_id is None:
 +        potential = repository.all_revision_ids()
 +    else:
 +        potential = itertools.imap(lambda (rev, parents): rev, 
 +                graph.iter_ancestry([revision_id]))
 +    upgrade_map = generate_upgrade_map(new_mapping, potential, mapping_registry)
 +   
 +    # Make sure all the required current version revisions are present
 +    for revid in upgrade_map.values():
 +        if not repository.has_revision(revid):
 +            repository.fetch(foreign_repository, revid)
 +
 +    if not allow_changes:
 +        for oldrevid, newrevid in upgrade_map.items():
 +            oldrev = repository.get_revision(oldrevid)
 +            newrev = repository.get_revision(newrevid)
 +            check_revision_changed(oldrev, newrev)
 +
 +    if revision_id is None:
 +        heads = repository.all_revision_ids() 
 +    else:
 +        heads = [revision_id]
 +
 +    plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map, 
 +      graph,
 +      lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
 +    def remove_parents((oldrevid, (newrevid, parents))):
 +        return (oldrevid, newrevid)
 +    upgrade_map.update(dict(map(remove_parents, plan.items())))
 +
 +    return (plan, upgrade_map)
 +
 + 
 +def upgrade_repository(repository, foreign_repository, new_mapping, 
 +                       mapping_registry, revision_id=None, allow_changes=False, 
 +                       verbose=False):
 +    """Upgrade the revisions in repository until the specified stop revision.
 +
 +    :param repository: Repository in which to upgrade.
 +    :param foreign_repository: Repository to fetch new revisions from.
 +    :param new_mapping: New mapping.
 +    :param revision_id: Revision id up until which to upgrade, or None for 
 +                        all revisions.
 +    :param allow_changes: Allow changes to mappings.
 +    :param verbose: Whether to print list of rewrites
 +    :return: Dictionary of mapped revisions
 +    """
 +    check_rebase_version(MIN_REBASE_VERSION)
 +    from bzrlib.plugins.rebase.rebase import (
 +        replay_snapshot, rebase, rebase_todo)
 +
 +    # Find revisions that need to be upgraded, create
 +    # dictionary with revision ids in key, new parents in value
 +    try:
 +        repository.lock_write()
 +        foreign_repository.lock_read()
 +        (plan, revid_renames) = create_upgrade_plan(repository, foreign_repository, 
 +                                                    new_mapping, mapping_registry,
 +                                                    revision_id=revision_id,
 +                                                    allow_changes=allow_changes)
 +        if verbose:
 +            for revid in rebase_todo(repository, plan):
 +                info("%s -> %s" % (revid, plan[revid][0]))
 +        def fix_revid(revid):
 +            try:
 +                (foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
 +            except InvalidRevisionId:
 +                return revid
 +            return new_mapping.revision_id_foreign_to_bzr(foreign_revid)
 +        def replay(repository, oldrevid, newrevid, new_parents):
 +            return replay_snapshot(repository, oldrevid, newrevid, new_parents,
 +                                   revid_renames, fix_revid)
 +        rebase(repository, plan, replay)
 +        return revid_renames
 +    finally:
 +        repository.unlock()
 +        foreign_repository.unlock()
 +
diff --cc log.py
index fd439241109edf3348b9a296a722250650f5340b,0000000000000000000000000000000000000000..9193f8e2e5cb2902ed46e1eb38cc0e2571dcfea4
mode 100644,000000..100644
--- 1/log.py
--- /dev/null
+++ b/log.py
@@@ -1,41 -1,0 +1,41 @@@
-             (uuid, bp, revnum, mapp) = mapping.mapping_registry.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):
 +    """Custom log displayer for Subversion revisions.
 +
 +    :param rev: Revision object.
 +    """
 +    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.mapping_registry.parse_revision_id(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 e9350ebe65b2e7387b42e7ce6f0877f5b0f14abf,0000000000000000000000000000000000000000..57921cc5b5436a803c7f476a4d13e86adb09d5df
mode 100644,000000..100644
--- /dev/null
@@@ -1,785 -1,0 +1,785 @@@
-         :return: Tuple with uuid, branch path, revision number and mapping.
 +# 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.revision import NULL_REVISION
 +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_REVISION_INFO = 'bzr:revision-info'
 +SVN_PROP_BZR_REVISION_ID = 'bzr:revision-id:'
 +SVN_PROP_BZR_TEXT_REVISIONS = 'bzr:text-revisions'
 +SVN_PROP_BZR_TEXT_PARENTS = 'bzr:text-parents'
 +SVN_PROP_BZR_LOG = 'bzr:log'
 +SVN_PROP_BZR_REQUIRED_FEATURES = 'bzr:required-features'
 +SVN_PROP_BZR_HIDDEN = 'bzr:hidden'
 +
 +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_TEXT_REVISIONS = 'bzr:text-revisions'
 +SVN_REVPROP_BZR_REQUIRED_FEATURES = 'bzr:required-features'
 +SVN_REVPROP_BZR_BASE_REVISION = 'bzr:base-revision'
 +SVN_REVPROP_BZR_SKIP = 'bzr:skip'
 +SVN_REVPROP_BZR_HIDDEN = 'bzr:hidden'
 +SVN_REVPROP_BZR_TAGS = 'bzr:tags'
 +
 +
 +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_log(log):
 +    if log is None:
 +        return None
 +    try:
 +        return log.decode("utf-8")
 +    except UnicodeDecodeError:
 +        return log
 +
 +
 +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 = parse_svn_log(svn_revprops.get(properties.PROP_REVISION_LOG))
 +
 +    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_tags_property(text):
 +    for name, revid in [line.split("\t") for line in text.splitlines()]:
 +        if revid == "":
 +            yield name.decode("utf-8"), None
 +        else:
 +            yield name.decode("utf-8"), revid
 +
 +
 +def generate_tags_property(tags):
 +    ret = ""
 +    for name in sorted(tags):
 +        ret += "%s\t" % name.encode("utf-8")
 +        if tags[name] is not None:
 +            ret += tags[name]
 +        ret += "\n"
 +    return ret
 +
 +
 +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
 +
 +
 +def parse_required_features_property(text):
 +    return set(text.split(","))
 +
 +class BzrSvnMapping(foreign.VcsMapping):
 +    """Class that maps between Subversion and Bazaar semantics."""
 +    experimental = False
 +    _warned_experimental = False
 +    roundtripping = False
 +    can_use_revprops = False
 +    can_use_fileprops = False
 +    supports_hidden = 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 from_revprops(cls, revprops):
 +        raise NotImplementedError
 +
 +    def check_layout(self, repository, layout):
 +        """Check whether a layout can be used with this mapping."""
 +        pass
 +
 +    def get_mandated_layout(self, repository):
 +        """Return the repository layout if any is mandated by this mapping, 
 +        None otherwise."""
 +        return None
 +
 +    def get_guessed_layout(self, repository):
 +        """Return the repository layout guessed by this mapping or None.
 +        """
 +        return None
 +
 +    def revision_id_bzr_to_foreign(self, revid):
 +        """Parse an existing Subversion-based revision id.
 +
 +        :param revid: The revision id.
 +        :raises: InvalidRevisionId
-     def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
++        :return: Tuple with (uuid, branch path, revision number) and mapping.
 +        """
 +        raise NotImplementedError(self.revision_id_bzr_to_foreign)
 +
-         :param revnum: Subversion revision number.
++    def revision_id_foreign_to_bzr(self, (uuid, path, revnum)):
 +        """Generate a unambiguous revision id. 
 +        
 +        :param uuid: UUID of the repository.
-         :return: tuple with (uuid, branch_path, revno, mapping)
 +        :param path: Branch path.
++        :param revnum: Subversion revision number.
 +
 +        :return: New revision id.
 +        """
 +        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
 +
 +    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_lhs_parent(self, branch_path, revprops, fileprops):
 +        """Determine the left hand side parent, if it was explicitly recorded.
 +
 +        If not explicitly recorded, returns None. Returns NULL_REVISION if 
 +        there is no lhs parent.
 +
 +        """
 +        return None
 +
 +    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, 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, 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_text_revisions(self, text_revisions, revprops, fileprops):
 +        raise NotImplementedError(self.export_text_revisions)
 +
 +    def import_text_revisions(self, revprops, fileprops):
 +        raise NotImplementedError(self.import_text_revisions)
 +
 +    def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops):
 +        """Determines the revision properties and branch root file 
 +        properties.
 +        """
 +        raise NotImplementedError(self.export_revision)
 +
 +    def export_message(self, log, revprops, fileprops):
 +        raise NotImplementedError(self.export_message)
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        raise NotImplementedError(self.get_revision_id)
 +
 +    @classmethod
 +    def get_test_instance(cls):
 +        return cls()
 +
 +    def is_bzr_revision_hidden(self, revprops, changed_fileprops):
 +        return False
 +
 +    def export_hidden(self, revprops, fileprops):
 +        raise NotImplementedError(self.export_hidden)
 +
 +
 +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).decode("utf-8")] = 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():
 +        parts = line.split("\t")
 +        entry = parts[0]
 +        ret[urllib.unquote(entry)] = filter(lambda x: x != "", [osutils.safe_revision_id(parent_revid) for parent_revid in parts[1:]])
 +    return ret
 +
 +
 +def parse_text_revisions_property(text):
 +    ret = {}
 +    for line in text.splitlines():
 +        (entry, revid) = line.split("\t", 1)
 +        ret[urllib.unquote(entry)] = osutils.safe_revision_id(revid)
 +    return ret
 +
 +
 +def generate_text_parents_property(text_parents):
 +    return "".join(["%s\t%s\n" % (urllib.quote(path.encode("utf-8")), "\t".join(text_parents[path])) for path in sorted(text_parents.keys())])
 +
 +
 +def generate_text_revisions_property(text_revisions):
 +    return "".join(["%s\t%s\n" % (urllib.quote(path.encode("utf-8")), text_revisions[path]) for path in sorted(text_revisions.keys())])
 +
 +
 +class BzrSvnMappingFileProps(object):
 +    def __init__(self, name):
 +        self.name = name
 +
 +    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_revisions(self, svn_revprops, fileprops):
 +        metadata = fileprops.get(SVN_PROP_BZR_TEXT_REVISIONS)
 +        if metadata is None:
 +            return {}
 +        return parse_text_revisions_property(metadata)
 +
 +    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, text_parents, svn_revprops, fileprops):
 +        if text_parents != {}:
 +            fileprops[SVN_PROP_BZR_TEXT_PARENTS] = generate_text_parents_property(text_parents)
 +        elif SVN_PROP_BZR_TEXT_PARENTS in fileprops:
 +            fileprops[SVN_PROP_BZR_TEXT_PARENTS] = ""
 +
 +    def export_text_revisions(self, text_revisions, svn_revprops, fileprops):
 +        if text_revisions != {}:
 +            fileprops[SVN_PROP_BZR_TEXT_REVISIONS] = generate_text_revisions_property(text_revisions)
 +        elif SVN_PROP_BZR_TEXT_REVISIONS in fileprops:
 +            fileprops[SVN_PROP_BZR_TEXT_REVISIONS] = ""
 +
 +    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, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops):
 +
 +        # Keep track of what Subversion properties to set later on
 +        svn_fileprops[SVN_PROP_BZR_REVISION_INFO] = generate_revision_metadata(
 +            timestamp, timezone, committer, revprops)
 +
 +        if len(parent_ids) > 1:
 +            svn_fileprops.update(self._record_merges(parent_ids[1:], svn_fileprops))
 +
 +        # Set appropriate property if revision id was specified by 
 +        # caller
 +        if revision_id is not None:
 +            old = svn_fileprops.get(SVN_PROP_BZR_REVISION_ID+self.name, "")
 +            svn_fileprops[SVN_PROP_BZR_REVISION_ID+self.name] = old + "%d %s\n" % (revno, revision_id)
 +
 +    def export_message(self, message, revprops, fileprops):
 +        fileprops[SVN_PROP_BZR_LOG] = message.encode("utf-8")
 +
 +    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, fileids, revprops, fileprops):
 +        if fileids != {}:
 +            file_id_text = generate_fileid_property(fileids)
 +            fileprops[SVN_PROP_BZR_FILEIDS] = file_id_text
 +        elif SVN_PROP_BZR_FILEIDS in fileprops:
 +            fileprops[SVN_PROP_BZR_FILEIDS] = ""
 +
 +
 +class BzrSvnMappingRevProps(object):
 +    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, text_parents, svn_revprops, fileprops):
 +        if text_parents != {}:
 +            svn_revprops[SVN_REVPROP_BZR_TEXT_PARENTS] = generate_text_parents_property(text_parents)
 +
 +    def import_text_revisions(self, svn_revprops, fileprops):
 +        if not svn_revprops.has_key(SVN_REVPROP_BZR_TEXT_REVISIONS):
 +            return {}
 +        return parse_text_revisions_property(svn_revprops[SVN_REVPROP_BZR_TEXT_REVISIONS])
 +
 +    def export_text_revisions(self, text_revisions, svn_revprops, fileprops):
 +        if text_revisions != {}:
 +            svn_revprops[SVN_REVPROP_BZR_TEXT_REVISIONS] = generate_text_revisions_property(text_revisions)
 +
 +    def get_lhs_parent(self, branch_parent, svn_revprops, fileprops):
 +        return svn_revprops.get(SVN_REVPROP_BZR_BASE_REVISION)
 +
 +    def get_rhs_parents(self, branch_path, svn_revprops, fileprops):
 +        return tuple(svn_revprops.get(SVN_REVPROP_BZR_MERGE, "").splitlines())
 +
 +    def get_branch_root(self, revprops):
 +        return revprops[SVN_REVPROP_BZR_ROOT]
 +
 +    def get_revision_id(self, branch_path, revprops, fileprops):
 +        if not is_bzr_revision_revprops(revprops) or not SVN_REVPROP_BZR_REVISION_ID in revprops:
 +            return (None, None)
 +        revid = revprops[SVN_REVPROP_BZR_REVISION_ID]
 +        revno = int(revprops[SVN_REVPROP_BZR_REVNO])
 +        return (revno, revid)
 +
 +    def export_message(self, message, revprops, fileprops):
 +        revprops[SVN_REVPROP_BZR_LOG] = message.encode("utf-8")
 +
 +    def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops):
 +
 +        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 len(parent_ids) > 1:
 +            svn_revprops[SVN_REVPROP_BZR_MERGE] = "".join([x+"\n" for x in parent_ids[1:]])
 +        if len(parent_ids) == 0:
 +            svn_revprops[SVN_REVPROP_BZR_BASE_REVISION] = NULL_REVISION
 +        else:
 +            svn_revprops[SVN_REVPROP_BZR_BASE_REVISION] = parent_ids[0]
 +        
 +        svn_revprops[SVN_REVPROP_BZR_REVNO] = str(revno)
 +
 +    def export_fileid_map(self, fileids, revprops, fileprops):
 +        if fileids != {}:
 +            revprops[SVN_REVPROP_BZR_FILEIDS] = generate_fileid_property(fileids)
 +
 +    def get_rhs_ancestors(self, branch_path, revprops, fileprops):
 +        raise NotImplementedError(self.get_rhs_ancestors)
 +
 +
 +class SubversionMappingRegistry(foreign.VcsMappingRegistry):
 +
 +    def parse_mapping_name(self, name):
 +        assert isinstance(name, str)
 +        if "-" in name:
 +            name, rest = name.split("-", 1)
 +            assert isinstance(rest, str)
 +            return self.get(name)(rest)
 +        return self.get(name)()
 +
 +
 +    def parse_revision_id(self, revid):
 +        """Try to parse a Subversion revision id.
 +        
 +        :param revid: Revision id to parse
- mapping_registry.set_default('v4')
++        :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 = self.get(mapping_version)
 +        return mapping.revision_id_bzr_to_foreign(revid)
 +
 +
 +mapping_registry = SubversionMappingRegistry()
 +mapping_registry.register_lazy('v1', 'bzrlib.plugins.svn.mapping2', 
 +                               'BzrSvnMappingv1', 
 +                               'Original bzr-svn mapping format (bzr-svn 0.2.x)')
 +mapping_registry.register_lazy('v2', 'bzrlib.plugins.svn.mapping2',
 +                               'BzrSvnMappingv2', 
 +                               'Second format (bzr-svn 0.3.x)')
 +mapping_registry.register_lazy('v3', 'bzrlib.plugins.svn.mapping3', 
 +                               'BzrSvnMappingv3FileProps', 
 +                               'Third format (bzr-svn 0.4.x)')
 +mapping_registry.register_lazy('v4', 'bzrlib.plugins.svn.mapping4', 
 +                               'BzrSvnMappingv4',
 +                               'Fourth format (bzr-svn 0.5.x)')
++mapping_registry.set_default('v3')
 +
 +def find_mapping(revprops, fileprops):
 +    if SVN_REVPROP_BZR_MAPPING_VERSION in revprops:
 +        try:
 +            cls = mapping_registry.get(revprops[SVN_REVPROP_BZR_MAPPING_VERSION])
 +            ret = cls.from_revprops(revprops)
 +        except KeyError:
 +            pass
 +        except NotImplementedError:
 +            pass
 +        else:
 +            if ret is not None:
 +                return ret
 +    for k, v in fileprops.items():
 +        if k.startswith(SVN_PROP_BZR_REVISION_ID):
 +            return mapping_registry.parse_mapping_name(k[len(SVN_PROP_BZR_REVISION_ID):])
 +    return None
 +
 +
 +def is_bzr_revision_revprops(revprops):
 +    if revprops.has_key(SVN_REVPROP_BZR_MAPPING_VERSION):
 +        return True
 +    if revprops.has_key(SVN_REVPROP_BZR_SKIP):
 +        return False
 +    return None
 +
 +
 +def is_bzr_revision_fileprops(fileprops):
 +    for k in fileprops:
 +        if k.startswith(SVN_PROP_BZR_PREFIX):
 +            return True
 +    return None
 +
 +
 +def estimate_bzr_ancestors(fileprops):
 +    found = []
 +    for k, v in fileprops.items():
 +        if k.startswith(SVN_PROP_BZR_REVISION_ID):
 +            found.append(len(v.splitlines()))
 +    if found != []:
 +        return sorted(found, reverse=True)[0]
 +    for k in fileprops:
 +        if k.startswith(SVN_PROP_BZR_PREFIX):
 +            return 1
 +    return 0
 +
 +
 +def get_roundtrip_ancestor_revids(fileprops):
 +    for propname, propvalue in fileprops.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:
 +                (revno, revid) = parse_revid_property(line)
 +                yield (revid, revno, mapping_name)
 +            except errors.InvalidPropertyValue, ie:
 +                mutter(str(ie))
 +
diff --cc mapping2.py
index 09fdde95042a7f4ec4f56f0b338a7062f21133dc,0000000000000000000000000000000000000000..31465e6c2d4c111582fbbe14f78ae957d81ef38d
mode 100644,000000..100644
--- /dev/null
@@@ -1,213 -1,0 +1,213 @@@
-         return (uuid, branch_path, revnum, cls(LegacyLayout.from_branch_path(branch_path)))
 +# 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, NotBranchError
 +from bzrlib.inventory import ROOT_ID
 +from bzrlib.plugins.svn.errors import LayoutUnusable
 +from bzrlib.plugins.svn.layout import RepositoryLayout, get_root_paths, RootLayout, TrunkLayout
 +from bzrlib.plugins.svn.mapping import BzrSvnMapping, escape_svn_path, unescape_svn_path, parse_svn_revprops
 +
 +SVN_PROP_BZR_MERGE = 'bzr:merge'
 +
 +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.
 +    """
 +    name = "v1"
 +    roundtripping = False
 +
 +    def __init__(self, layout):
 +        super(BzrSvnMappingv1, self).__init__()
 +        self._layout = layout
 +
 +    @classmethod
 +    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
-     def revision_id_foreign_to_bzr(cls, (uuid, revnum, path)):
++        return (uuid, branch_path, revnum), cls(LegacyLayout.from_branch_path(branch_path))
 +
 +    @classmethod
-         return "%s-%s" % (self.revision_id_foreign_to_bzr((uuid, revnum, branch)), escape_svn_path(inv_path.encode("utf-8")))
++    def revision_id_foreign_to_bzr(cls, (uuid, path, revnum)):
 +        assert isinstance(path, str)
 +        return "svn-v1:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other)
 +
 +    def is_branch(self, branch_path):
 +        return self._layout.is_branch(branch_path)
 +
 +    def is_tag(self, tag_path):
 +        return False
 +
 +    def import_revision(self, svn_revprops, fileprops, uuid, branch, revnum, rev):
 +        parse_svn_revprops(svn_revprops, rev)
 +
 +    def generate_file_id(self, uuid, revnum, branch, inv_path):
 +        if inv_path == u"":
 +            return ROOT_ID
-         return (uuid, branch_path, revnum, cls(LegacyLayout.from_branch_path(branch_path)))
++        return "%s-%s" % (self.revision_id_foreign_to_bzr((uuid, branch, revnum)), escape_svn_path(inv_path.encode("utf-8")))
 +
 +    def import_fileid_map(self, revprops, fileprops):
 +        return {}
 +
 +    def import_text_parents(self, revprops, fileprops):
 +        return {}
 +
 +    def import_text_revisions(self, svn_revprops, fileprops):
 +        return {}
 +
 +    def get_rhs_parents(self, branch_path, revprops, fileprops):
 +        value = fileprops.get(SVN_PROP_BZR_MERGE, "")
 +        if value == "":
 +            return ()
 +        return (value.splitlines()[-1])
 +
 +    @classmethod
 +    def from_repository(cls, repository, _hinted_branch_path=None):
 +        if _hinted_branch_path is None:
 +            return cls(TrunkLegacyLayout())
 +    
 +        return cls(LegacyLayout.from_branch_path(_hinted_branch_path))
 +
 +    @classmethod
 +    def get_test_instance(cls):
 +        return cls(TrunkLegacyLayout())
 +
 +    def get_guessed_layout(self, repository):
 +        return self._layout
 +
 +    def check_layout(self, repository, layout):
 +        if isinstance(layout, RootLayout):
 +            self._layout = RootLegacyLayout()
 +        elif isinstance(layout, TrunkLayout):
 +            self._layout = TrunkLegacyLayout(layout.level or 0)
 +        else:
 +            raise LayoutUnusable(layout, self)
 +
 +
 +class BzrSvnMappingv2(BzrSvnMappingv1):
 +    """The second version of the mappings as used in the 0.3.x series.
 +
 +    It does not support pushing revisions to Subversion as-is, but only 
 +    as part of a merge.
 +    """
 +    name = "v2"
 +    roundtripping = False
 +
 +    @classmethod
 +    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
-     def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
++        return (uuid, branch_path, revnum), cls(LegacyLayout.from_branch_path(branch_path))
 +
++    def revision_id_foreign_to_bzr(self, (uuid, path, revnum)):
 +        return "svn-v2:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
 +
 +    def __eq__(self, other):
 +        return type(self) == type(other)
 +
 +
 +class LegacyLayout(RepositoryLayout):
 +
 +    def get_tag_path(self, name, project=""):
 +        return None
 +
 +    def get_branch_path(self, name, project=""):
 +        return None
 +
 +    @classmethod
 +    def from_branch_path(cls, path):
 +        parts = path.strip("/").split("/")
 +        for i in range(0,len(parts)):
 +            if parts[i] == "trunk" or \
 +               parts[i] == "branches" or \
 +               parts[i] == "tags":
 +                return TrunkLegacyLayout(level=i)
 +
 +        return RootLegacyLayout()
 +
 +
 +class TrunkLegacyLayout(LegacyLayout):
 +
 +    def __init__(self, level=0):
 +        super(TrunkLegacyLayout, self).__init__()
 +        self.level = level
 +    
 +    def parse(self, path):
 +        parts = path.strip("/").split("/")
 +        if len(parts) == 0 or self.level >= len(parts):
 +            raise NotBranchError(path=path)
 +
 +        if parts[self.level] == "trunk" or parts[self.level] == "hooks":
 +            return ("branch", "/".join(parts[0:self.level]), "/".join(parts[0:self.level+1]).strip("/"), 
 +                    "/".join(parts[self.level+1:]).strip("/"))
 +        elif ((parts[self.level] == "tags" or parts[self.level] == "branches") and 
 +              len(parts) >= self.level+2):
 +            return ("branch", "/".join(parts[0:self.level]), "/".join(parts[0:self.level+2]).strip("/"), 
 +                    "/".join(parts[self.level+2:]).strip("/"))
 +        else:
 +            raise NotBranchError(path=path)
 +
 +    def is_branch(self, path, project=None):
 +        parts = path.strip("/").split("/")
 +        if len(parts) == self.level+1 and parts[self.level] == "trunk":
 +            return True
 +
 +        if len(parts) == self.level+2 and \
 +           (parts[self.level] == "branches" or parts[self.level] == "tags"):
 +            return True
 +
 +        return False
 +
 +    def get_branches(self, repository, revnum, project="", pb=None):
 +        return get_root_paths(repository, 
 +             [("*/" * self.level) + x for x in "branches/*", "tags/*", "trunk"], 
 +             revnum, self.is_branch, project)
 +
 +    def get_tags(self, repository, revnum, project="", pb=None):
 +        return []
 +
 +
 +class RootLegacyLayout(LegacyLayout):
 +
 +    def parse(self, path):
 +        return ("branch", "", "", path)
 +
 +    def is_branch(self, path, project=None):
 +        return path == ""
 +
 +    def get_branches(self, repository, revnum, project="", pb=None):
 +        return [("", "", "trunk")]
 +
 +    def get_tags(self, repository, revnum, project="", pb=None):
 +        return []
index e4ed233e487ade1c94cdbfb5e0e9fd598762cc58,0000000000000000000000000000000000000000..617b64f1bea77fd5e9944218ef3b353a5ebbb72a
mode 100644,000000..100644
--- /dev/null
@@@ -1,311 -1,0 +1,311 @@@
-         return (uuid, branch_path, srevnum, cls(scheme))
 +# 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.layout import RepositoryLayout, get_root_paths
 +from bzrlib.plugins.svn.mapping3.scheme import (BranchingScheme, guess_scheme_from_branch_path, 
 +                             guess_scheme_from_history, ListBranchingScheme, 
 +                             scheme_from_layout,
 +                             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
 +
 +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 supports_tags(self):
 +        return (self.scheme.tag_list != [])
 +
 +    def get_branches(self, repository, revnum, project=None, pb=None):
 +        return get_root_paths(repository, self.scheme.branch_list, revnum, self.scheme.is_branch, project, pb)
 +
 +    def get_tags(self, repository, revnum, project=None, pb=None):
 +        return get_root_paths(repository, 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 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 True
 +        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)
 +
 +
 +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-"
 +    roundtripping = True
 +    can_use_fileprops = True
 +
 +    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
 +        self.name = "v3-" + str(scheme)
 +
 +    @classmethod
 +    def from_revprops(cls, revprops):
 +        return None
 +
 +    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)
 +
 +    def check_layout(self, repository, layout):
 +        self.scheme = scheme_from_layout(layout)
 +        config_set_scheme(repository, self.scheme, 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)
 +
 +    @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 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)
 +
-     def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
++        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, path, revnum)):
 +        return self._generate_revision_id(uuid, revnum, path, self.scheme)
 +
 +    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))
 +
 +    @classmethod
 +    def get_test_instance(cls):
 +        return cls(NoBranchingScheme())
 +
 +
 +class BzrSvnMappingv3FileProps(mapping.BzrSvnMappingFileProps, BzrSvnMappingv3):
 +
 +    def __init__(self, scheme, guessed_scheme=None):
 +        BzrSvnMappingv3.__init__(self, scheme, guessed_scheme)
 +        self.revprop_map = mapping.BzrSvnMappingRevProps()
 +
 +    def export_text_parents(self, text_parents, svn_revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_text_parents(self, text_parents, svn_revprops, fileprops)
 +        if svn_revprops is not None:
 +            self.revprop_map.export_text_parents(text_parents, svn_revprops, fileprops)
 +
 +    def export_text_revisions(self, text_revisions, svn_revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_text_revisions(self, text_revisions, svn_revprops, fileprops)
 +        if svn_revprops is not None:
 +            self.revprop_map.export_text_revisions(text_revisions, svn_revprops, fileprops)
 +
 +    def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops):
 +        mapping.BzrSvnMappingFileProps.export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops)
 +        if svn_revprops is not None:
 +            self.revprop_map.export_revision(branch_root, timestamp, timezone, committer, revprops, None, revno, parent_ids, svn_revprops, svn_fileprops)
 +
 +    def export_fileid_map(self, fileids, revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_fileid_map(self, fileids, revprops, fileprops)
 +        if revprops is not None:
 +            self.revprop_map.export_fileid_map(fileids, revprops, fileprops)
 +
 +    def export_message(self, log, revprops, fileprops):
 +        mapping.BzrSvnMappingFileProps.export_message(self, log, revprops, fileprops)
 +        if revprops is not None:
 +            self.revprop_map.export_message(log, revprops, fileprops)
 +
diff --cc mapping4.py
index 8ea2dd7c525fd2c32fd6049b4c46f9bc1cba380b,0000000000000000000000000000000000000000..443fc6a866cae670fd336469f279ef5b1c85eb30
mode 100644,000000..100644
--- /dev/null
@@@ -1,182 -1,0 +1,182 @@@
-         return (uuid, branch_path, int(srevnum), cls())
 +# 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 layout, 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
 +    roundtripping = True
 +    can_use_revprops = True
 +    can_use_fileprops = True
 +    supports_hidden = True
 +
 +    def __init__(self, layout=None):
 +        self.name = "v4"
 +        self.layout = layout
 +        self.revprops = mapping.BzrSvnMappingRevProps()
 +        self.fileprops = mapping.BzrSvnMappingFileProps(self.name)
 +
 +    @classmethod
 +    def from_repository(cls, repository, _hinted_branch_path=None):
 +        if _hinted_branch_path == "":
 +            return cls(layout.RootLayout())
 +        else:
 +            return cls(layout.TrunkLayout())
 +
 +    @classmethod
 +    def from_revprops(cls, revprops):
 +        return cls()
 +
 +    @classmethod
 +    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)
 +
-     def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
++        return (uuid, branch_path, int(srevnum)), cls()
 +
++    def revision_id_foreign_to_bzr(self, (uuid, path, revnum)):
 +        return "svn-v4:%s:%s:%d" % (uuid, path, revnum)
 +
 +    def generate_file_id(self, uuid, revnum, branch, inv_path):
 +        return "%d@%s:%s" % (revnum, uuid, mapping.escape_svn_path("%s/%s" % (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_branch_root(self, revprops):
 +        return self.revprops.get_branch_root(revprops)
 +
 +    def get_lhs_parent(self, branch_path, svn_revprops, fileprops):
 +        return self.revprops.get_lhs_parent(branch_path, svn_revprops, fileprops)
 +
 +    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_text_revisions(self, svn_revprops, fileprops):
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_TEXT_REVISIONS):
 +            return self.revprops.import_text_revisions(svn_revprops, fileprops)
 +        else:
 +            return self.fileprops.import_text_revisions(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, branch_root, timestamp, timezone, committer, revprops, revision_id, 
 +                        revno, parent_ids, svn_revprops, svn_fileprops):
 +        if svn_revprops is not None:
 +            self.revprops.export_revision(branch_root, timestamp, timezone, committer, 
 +                                          revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops)
 +            svn_revprops[mapping.SVN_REVPROP_BZR_MAPPING_VERSION] = self.name
 +        else:
 +            self.fileprops.export_revision(branch_root, timestamp, timezone, committer, 
 +                                      revprops, revision_id, revno, parent_ids, svn_revprops, svn_fileprops)
 +
 +    def export_fileid_map(self, fileids, revprops, fileprops):
 +        if revprops is not None:
 +            self.revprops.export_fileid_map(fileids, revprops, fileprops)
 +        else:
 +            self.fileprops.export_fileid_map(fileids, revprops, fileprops)
 +
 +    def export_text_parents(self, text_parents, revprops, fileprops):
 +        if revprops is not None:
 +            self.revprops.export_text_parents(text_parents, revprops, fileprops)
 +        else:
 +            self.fileprops.export_text_parents(text_parents, revprops, fileprops)
 +
 +    def export_text_revisions(self, text_revisions, revprops, fileprops):
 +        if revprops is not None:
 +            self.revprops.export_text_revisions(text_revisions, revprops, fileprops)
 +        else:
 +            self.fileprops.export_text_revisions(text_revisions, 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 = mapping.parse_required_features_property(svn_revprops[mapping.SVN_REVPROP_BZR_REQUIRED_FEATURES])
 +            assert features.issubset(supported_features), "missing feature: %r" % features.difference(supported_features)
 +        if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
 +            assert svn_revprops[mapping.SVN_REVPROP_BZR_MAPPING_VERSION] == self.name, "unknown mapping: %s" % svn_revprops[mapping.SVN_REVPROP_BZR_MAPPING_VERSION]
 +            self.revprops.import_revision(svn_revprops, fileprops, uuid, branch, revnum, rev)
 +        else:
 +            if fileprops.has_key(mapping.SVN_PROP_BZR_REQUIRED_FEATURES):
 +                features = mapping.parse_required_features_property(fileprops[mapping.SVN_PROP_BZR_REQUIRED_FEATURES])
 +                assert features.issubset(supported_features), "missing feature: %r" % features.difference(supported_features)
 +            self.fileprops.import_revision(svn_revprops, fileprops, uuid, branch, revnum, rev)
 +
 +    def get_mandated_layout(self, repository):
 +        return self.layout
 +
 +    def is_bzr_revision_hidden(self, revprops, changed_fileprops):
 +        if revprops.has_key(mapping.SVN_REVPROP_BZR_HIDDEN):
 +            return True
 +        if (changed_fileprops.has_key(mapping.SVN_PROP_BZR_HIDDEN) and 
 +            changed_fileprops.get(mapping.SVN_PROP_BZR_HIDDEN) is not None):
 +            return True
 +        return False
 +
 +    def get_hidden_lhs_ancestors_count(self, fileprops):
 +        return int(fileprops.get(mapping.SVN_PROP_BZR_HIDDEN, "0"))
 +
 +    def export_hidden(self, revprops, fileprops):
 +        if revprops is not None:
 +            revprops[mapping.SVN_REVPROP_BZR_HIDDEN] = ""
 +            return
 +        old_value = fileprops.get(mapping.SVN_PROP_BZR_HIDDEN, "0")
 +        fileprops[mapping.SVN_PROP_BZR_HIDDEN] = str(int(old_value)+1)
diff --cc revids.py
index 69cc46877b91268817a6e498afcb01f6d21b681c,0000000000000000000000000000000000000000..211362cebd079f0e5beb8a9ae5755e37c9a98254
mode 100644,000000..100644
--- /dev/null
+++ b/revids.py
@@@ -1,310 -1,0 +1,310 @@@
-             (uuid, branch_path, revnum, mapping) = mapping_registry.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
 +
 +"""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 (BzrSvnMapping, 
 +                     SVN_PROP_BZR_REVISION_ID, parse_revid_property,
 +                     find_mapping, mapping_registry, is_bzr_revision_revprops)
 +
 +class RevidMap(object):
 +    def __init__(self, repos):
 +        self.repos = repos
 +
 +    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) = mapping_registry.parse_revision_id(revid)
++            (uuid, branch_path, revnum), mapping = mapping_registry.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
 +
 +        last_revnum = self.repos.get_latest_revnum()
 +        fileprops_to_revnum = last_revnum
 +        for entry_revid, branch, min_revno, max_revno, mapping in self.discover_revprop_revids(lyout, last_revnum, 0):
 +            if revid == entry_revid:
 +                return (branch, min_revno, max_revno, mapping.name)
 +            fileprops_to_revnum = min(fileprops_to_revnum, min_revno)
 +
 +        for entry_revid, branch, min_revno, max_revno, mapping in self.discover_fileprop_revids(layout, 0, fileprops_to_revnum, project):
 +            if revid == entry_revid:
 +                (bp, revnum, mapping_name) = self.bisect_revid_revnum(revid, branch, min_revno, max_revno)
 +                return (bp, revnum, mapping_name)
 +        raise NoSuchRevision(self, revid)
 +
 +    def discover_revprop_revids(self, layout, from_revnum, to_revnum):
 +        """Discover bzr-svn revision properties between from_revnum and to_revnum.
 +
 +        :return: First revision number on which a revision property was found, or None
 +        """
 +        if self.repos.transport.has_capability("log-revprops") != True:
 +            return
 +        for revmeta in self.repos._revmeta_provider.iter_all_changes(layout, None, from_revnum, to_revnum):
 +            if is_bzr_revision_revprops(revmeta.get_revprops()):
 +                mapping = find_mapping(revmeta.get_revprops(), {})
 +                revid = revmeta.get_revision_id(mapping)
 +                if revid is not None:
 +                    yield (revid, mapping.get_branch_root(revmeta.get_revprops()).strip("/"), revmeta.revnum, revmeta.revnum, mapping)
 +
 +    def discover_fileprop_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):
 +            assert isinstance(branch, str)
 +            assert isinstance(revno, int)
 +            # Look at their bzr:revision-id-vX
 +            revids = set()
 +            try:
 +                revmeta = self.repos._revmeta_provider.get_revision(branch, revno)
 +                for revid, bzr_revno, mapping_name in revmeta.get_roundtrip_ancestor_revids():
 +                    revids.add(((bzr_revno, revid), mapping_name))
 +            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, 0, revno, mapping_registry.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._revmeta_provider.iter_reverse_branch_changes(branch_path, max_revnum, min_revnum):
 +            for propname, propvalue in revmeta.get_changed_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 = mapping_registry.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_branch_revnum(self, revid, layout, project=None):
 +        # Try a simple parse
 +        try:
++            (uuid, branch_path, revnum), mapping = mapping_registry.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, mapping_registry.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 = None
 +            fileprops_to_revnum = last_revnum
 +            for entry_revid, branch, min_revno, max_revno, mapping in self.actual.discover_revprop_revids(layout, last_revnum, last_checked):
 +                fileprops_to_revnum = min(fileprops_to_revnum, min_revno)
 +                if entry_revid == revid:
 +                    found = (branch, min_revno, max_revno, mapping)
 +                if entry_revid not in self.revid_seen:
 +                    self.cache.insert_revid(entry_revid, branch, min_revno, max_revno, mapping.name)
 +                    self.revid_seen.add(entry_revid)
 +            for entry_revid, branch, min_revno, max_revno, mapping in self.actual.discover_fileprop_revids(layout, last_checked, fileprops_to_revnum, project):
 +                min_revno = max(last_checked, min_revno)
 +                if entry_revid == revid:
 +                    found = (branch, min_revno, max_revno, mapping)
 +                if entry_revid not in self.revid_seen:
 +                    self.cache.insert_revid(entry_revid, branch, min_revno, max_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 found is None:
 +                raise e
 +            (branch_path, min_revnum, max_revnum, mapping) = found
 +            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))
diff --cc revmeta.py
index 8258995161d006b4e71053dd5735dc60281cf5f9,0000000000000000000000000000000000000000..139838b5e95a3356f6a4b00316e11d9d9d2e8578
mode 100644,000000..100644
--- /dev/null
@@@ -1,611 -1,0 +1,614 @@@
-             return mapping.revision_id_foreign_to_bzr((self.uuid, self.revnum, self.branch_path))
 +# 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, ui
 +from bzrlib.revision import NULL_REVISION, Revision
 +
 +from bzrlib.plugins.svn import changes, core, errors as svn_errors, logwalker, properties
 +from bzrlib.plugins.svn.mapping import is_bzr_revision_fileprops, is_bzr_revision_revprops, estimate_bzr_ancestors, SVN_REVPROP_BZR_SIGNATURE, get_roundtrip_ancestor_revids
 +from bzrlib.plugins.svn.svk import (SVN_PROP_SVK_MERGE, svk_features_merged_since, 
 +                 parse_svk_feature, estimate_svk_ancestors)
 +
 +def full_paths(find_children, paths, bp, from_bp, from_rev):
 +    """Generate the changes creating a specified branch path.
 +
 +    :param find_children: Function that recursively lists all children 
 +                          of a path in a revision.
 +    :param paths: Paths dictionary to update
 +    :param bp: Branch path to create.
 +    :param from_bp: Path to look up children in
 +    :param from_rev: Revision to look up children in.
 +    """
 +    pb = ui.ui_factory.nested_progress_bar()
 +    try:
 +        for c in find_children(from_bp, from_rev, pb):
 +            paths[changes.rebase_path(c, from_bp, bp)] = ('A', None, -1)
 +    finally:
 +        pb.finished()
 +    return paths
 +
 +
 +class RevisionMetadata(object):
 +    """Object describing a revision with bzr semantics in a Subversion repository."""
 +
 +    def __init__(self, repository, check_revprops, get_fileprops_fn, logwalker, 
 +                 uuid, branch_path, revnum, paths, revprops, 
 +                 changed_fileprops=None, fileprops=None, 
 +                 metabranch=None):
 +        self.repository = repository
 +        self.check_revprops = check_revprops
 +        self._get_fileprops_fn = get_fileprops_fn
 +        self._log = logwalker
 +        self.branch_path = branch_path
 +        self._paths = paths
 +        self.revnum = revnum
 +        self._revprops = revprops
 +        self._changed_fileprops = changed_fileprops
 +        self._fileprops = fileprops
 +        self.metabranch = metabranch
 +        self.uuid = uuid
 +
 +    def __eq__(self, other):
 +        return (type(self) == type(other) and 
 +                self.branch_path == other.branch_path and
 +                self.revnum == other.revnum and
 +                self.uuid == other.uuid)
 +
 +    def __repr__(self):
 +        return "<RevisionMetadata for revision %d in repository %s>" % (self.revnum, repr(self.uuid))
 +
 +    def changes_branch_root(self):
 +        """Check whether the branch root was modified in this revision.
 +        """
 +        if self.knows_changed_fileprops():
 +            return self.get_changed_fileprops() != {}
 +        return self.branch_path in self.get_paths()
 +
 +    def get_paths(self):
 +        """Fetch the changed paths dictionary for this revision.
 +        """
 +        if self._paths is None:
 +            self._paths = self._log.get_revision_paths(self.revnum)
 +        return self._paths
 +
 +    def get_revision_id(self, mapping):
 +        """Determine the revision id for this revision.
 +        """
 +        if mapping.roundtripping:
 +            # See if there is a bzr:revision-id revprop set
 +            (_, revid) = mapping.get_revision_id(self.branch_path, self.get_revprops(), self.get_changed_fileprops())
 +        else:
 +            revid = None
 +
 +        # Or generate it
 +        if revid is None:
-     return mapping.revision_id_foreign_to_bzr((uuid, revnum, bp))
++            return mapping.revision_id_foreign_to_bzr((self.uuid, self.branch_path, self.revnum))
 +
 +        return revid
 +
 +    def get_fileprops(self):
 +        """Get the file properties set on the branch root.
 +        """
 +        if self._fileprops is None:
 +            self._fileprops = self._get_fileprops_fn(self.branch_path, self.revnum)
 +        return self._fileprops
 +
 +    def get_revprops(self):
 +        """Get the revision properties set on the revision."""
 +        if self._revprops is None:
 +            self._revprops = self._log.revprop_list(self.revnum)
 +
 +        return self._revprops
 +
 +    def knows_changed_fileprops(self):
 +        """Check whether the changed file properties can be cheaply retrieved."""
 +        if self._changed_fileprops is None:
 +            return False
 +        changed_fileprops = self.get_changed_fileprops()
 +        return isinstance(changed_fileprops, dict) or changed_fileprops.is_loaded
 +
 +    def knows_fileprops(self):
 +        """Check whether the file properties can be cheaply retrieved."""
 +        fileprops = self.get_fileprops()
 +        return isinstance(fileprops, dict) or fileprops.is_loaded
 +
 +    def knows_revprops(self):
 +        """Check whether all revision properties can be cheaply retrieved."""
 +        revprops = self.get_revprops()
 +        return isinstance(revprops, dict) or revprops.is_loaded
 +
 +    def get_previous_fileprops(self):
 +        """Return the file properties set on the branch root before this revision."""
 +        # Perhaps the metabranch already has the parent?
 +        prev = None
 +        if self.metabranch is not None:
 +            parentrevmeta = self.metabranch.get_lhs_parent(self)
 +            if parentrevmeta is not None:
 +                prev = (parentrevmeta.branch_path, parentrevmeta.revnum)
 +        if prev is None:
 +            prev = changes.find_prev_location(self.get_paths(), self.branch_path, self.revnum)
 +        if prev is None:
 +            return {}
 +        (prev_path, prev_revnum) = prev
 +        return self._get_fileprops_fn(prev_path, prev_revnum)
 +
 +    def get_changed_fileprops(self):
 +        """Determine the file properties changed in this revision."""
 +        if self._changed_fileprops is None:
 +            if self.changes_branch_root():
 +                self._changed_fileprops = logwalker.lazy_dict({}, properties.diff, self.get_fileprops(), self.get_previous_fileprops())
 +            else:
 +                self._changed_fileprops = {}
 +        return self._changed_fileprops
 +
 +    def get_lhs_parent_revmeta(self, mapping):
 +        assert (mapping.is_branch(self.branch_path) or 
 +                mapping.is_tag(self.branch_path)), "%s not valid in %r" % (self.branch_path, mapping)
 +        def get_next_parent(rm):
 +            if rm.metabranch is not None and rm.metabranch.mapping == mapping:
 +                # Perhaps the metabranch already has the parent?
 +                parentrevmeta = rm.metabranch.get_lhs_parent(rm)
 +                if parentrevmeta is not None:
 +                    return parentrevmeta
 +            # FIXME: Don't use self.repository.branch_prev_location,
 +            #        since it browses history
 +            return rm.repository._revmeta_provider.branch_prev_location(rm, mapping)
 +        nm = get_next_parent(self)
 +        while nm is not None and nm.is_hidden(mapping):
 +            nm = get_next_parent(nm)
 +        return nm
 +
 +    def get_lhs_parent(self, mapping):
 +        """Find the revid of the left hand side parent of this revision."""
 +        # Sometimes we can retrieve the lhs parent from the revprop data
 +        lhs_parent = mapping.get_lhs_parent(self.branch_path, self.get_revprops(), self.get_changed_fileprops())
 +        if lhs_parent is not None:
 +            return lhs_parent
 +        parentrevmeta = self.get_lhs_parent_revmeta(mapping)
 +        if parentrevmeta is None:
 +            return NULL_REVISION
 +        return parentrevmeta.get_revision_id(mapping)
 +
 +    def estimate_bzr_fileprop_ancestors(self):
 +        """Estimate how many ancestors with bzr file properties this revision has.
 +
 +        """
 +        if not self.consider_bzr_fileprops():
 +            # This revisions descendant doesn't have bzr fileprops set, so this one can't have them either.
 +            return 0
 +        return estimate_bzr_ancestors(self.get_fileprops())
 +
 +    def estimate_svk_fileprop_ancestors(self):
 +        """Estimate how many svk ancestors this revision has."""
 +        if not self.consider_svk_fileprops():
 +            # This revisions descendant doesn't have svk fileprops set, so this one can't have them either.
 +            return 0
 +        return estimate_svk_ancestors(self.get_fileprops())
 +
 +    def is_bzr_revision_revprops(self):
 +        return is_bzr_revision_revprops(self.get_revprops())
 +
 +    def is_bzr_revision_fileprops(self):
 +        return is_bzr_revision_fileprops(self.get_changed_fileprops())
 +
 +    def is_hidden(self, mapping):
 +        if not mapping.supports_hidden:
 +            return False
 +        if self.consider_bzr_fileprops() or self.consider_bzr_revprops():
 +            return mapping.is_bzr_revision_hidden(self.get_revprops(), self.get_changed_fileprops())
 +        return False
 +
 +    def is_bzr_revision(self):
 +        """Determine (with as few network requests as possible) if this is a bzr revision.
 +
 +        """
 +        order = []
 +        # If the server already sent us all revprops, look at those first
 +        if self._log.quick_revprops:
 +            order.append(self.is_bzr_revision_revprops)
 +        if self.consider_bzr_fileprops():
 +            order.append(self.is_bzr_revision_fileprops)
 +        # Only look for revprops if they could've been committed
 +        if (not self._log.quick_revprops and self.consider_bzr_revprops()):
 +            order.append(self.is_bzr_revision_revprops)
 +        for fn in order:
 +            ret = fn()
 +            if ret is not None:
 +                return ret
 +        return None
 +
 +    def get_bzr_merges(self, mapping):
 +        return mapping.get_rhs_parents(self.branch_path, self.get_revprops(), self.get_changed_fileprops())
 +
 +    def get_svk_merges(self, mapping):
 +        if not self.consider_svk_fileprops():
 +            return ()
 +
 +        if not self.changes_branch_root():
 +            return ()
 +
 +        current = self.get_fileprops().get(SVN_PROP_SVK_MERGE, "")
 +        if current == "":
 +            return ()
 +
 +        previous = self.get_previous_fileprops().get(SVN_PROP_SVK_MERGE, "")
 +
 +        ret = []
 +        for feature in svk_features_merged_since(current, previous):
 +            # We assume svk:merge is only relevant on non-bzr-svn revisions. 
 +            # If this is a bzr-svn revision, the bzr-svn properties 
 +            # would be parsed instead.
 +            #
 +            # This saves one svn_get_dir() call.
 +            revid = svk_feature_to_revision_id(feature, mapping)
 +            if revid is not None:
 +                ret.append(revid)
 +
 +        return tuple(ret)
 +
 +    def get_distance_to_null(self, mapping):
 +        if mapping.roundtripping:
 +            (bzr_revno, _) = mapping.get_revision_id(self.branch_path, self.get_revprops(), 
 +                                                             self.get_changed_fileprops())
 +            if bzr_revno is not None:
 +                return bzr_revno
 +        return None
 +
 +    def get_hidden_lhs_ancestors_count(self, mapping):
 +        if not mapping.supports_hidden:
 +            return 0
 +        count = mapping.get_hidden_lhs_ancestors_count(self.get_fileprops())
 +        if count is not None:
 +            return count
 +        # FIXME: Count number of lhs ancestor revisions with bzr:hidden set
 +        return 0
 +
 +    def get_rhs_parents(self, mapping):
 +        """Determine the right hand side parents for this revision.
 +
 +        """
 +        if self.is_bzr_revision():
 +            return self.get_bzr_merges(mapping)
 +
 +        return self.get_svk_merges(mapping)
 +
 +    def get_parent_ids(self, mapping):
 +        lhs_parent = self.get_lhs_parent(mapping)
 +
 +        if lhs_parent == NULL_REVISION:
 +            return (NULL_REVISION,)
 +        else:
 +            return (lhs_parent,) + self.get_rhs_parents(mapping)
 +
 +    def get_signature(self):
 +        return self.get_revprops().get(SVN_REVPROP_BZR_SIGNATURE)
 +
 +    def get_revision(self, mapping):
 +        parent_ids = self.get_parent_ids(mapping)
 +
 +        if parent_ids == (NULL_REVISION,):
 +            parent_ids = ()
 +        rev = Revision(revision_id=self.get_revision_id(mapping), 
 +                       parent_ids=parent_ids,
 +                       inventory_sha1="")
 +
 +        rev.svn_meta = self
 +        rev.svn_mapping = mapping
 +
 +        mapping.import_revision(self.get_revprops(), self.get_changed_fileprops(), self.uuid, self.branch_path, 
 +                                self.revnum, rev)
 +
 +        return rev
 +
 +    def get_fileid_map(self, mapping):
 +        return mapping.import_fileid_map(self.get_revprops(), self.get_changed_fileprops())
 +
++    def get_text_revisions(self, mapping):
++        return mapping.import_text_revisions(self.get_revprops(), self.get_changed_fileprops())
++
 +    def consider_bzr_fileprops(self):
 +        return self.metabranch is None or self.metabranch.consider_bzr_fileprops(self)
 +
 +    def consider_bzr_revprops(self):
 +        return self.check_revprops
 +
 +    def consider_svk_fileprops(self):
 +        return self.metabranch is None or self.metabranch.consider_svk_fileprops(self)
 +
 +    def get_roundtrip_ancestor_revids(self):
 +        if not self.consider_bzr_fileprops():
 +            # This revisions descendant doesn't have bzr fileprops set, so this one can't have them either.
 +            return iter([])
 +        return iter(get_roundtrip_ancestor_revids(self.get_fileprops()))
 +
 +    def __hash__(self):
 +        return hash((self.__class__, self.uuid, self.branch_path, self.revnum))
 +
 +
 +class CachingRevisionMetadata(RevisionMetadata):
 +
 +    def __init__(self, repository, *args, **kwargs):
 +        super(CachingRevisionMetadata, self).__init__(repository, *args, **kwargs)
 +        self._parents_cache = getattr(self.repository._real_parents_provider, "_cache", None)
 +        self._revid_cache = self.repository.revmap.cache
 +        self._revid = None
 +
 +    def get_revision_id(self, mapping):
 +        if self._revid is not None:
 +            return self._revid
 +        # Look in the cache to see if it already has a revision id
 +        self._revid = self._revid_cache.lookup_branch_revnum(self.revnum, self.branch_path, mapping.name)
 +        if self._revid is not None:
 +            return self._revid
 +
 +        self._revid = super(CachingRevisionMetadata, self).get_revision_id(mapping)
 +
 +        self._revid_cache.insert_revid(self._revid, self.branch_path, self.revnum, self.revnum, mapping.name)
 +        self._revid_cache.commit_conditionally()
 +        return self._revid
 +
 +    def get_parent_ids(self, mapping):
 +        myrevid = self.get_revision_id(mapping)
 +
 +        if self._parents_cache is not None:
 +            parent_ids = self._parents_cache.lookup_parents(myrevid)
 +            if parent_ids is not None:
 +                return parent_ids
 +
 +        parent_ids = super(CachingRevisionMetadata, self).get_parent_ids(mapping)
 +
 +        self._parents_cache.insert_parents(myrevid, parent_ids)
 +
 +        return parent_ids
 +
 +
 +def svk_feature_to_revision_id(feature, mapping):
 +    """Convert a SVK feature to a revision id for this repository.
 +
 +    :param feature: SVK feature.
 +    :return: revision id.
 +    """
 +    try:
 +        (uuid, bp, revnum) = parse_svk_feature(feature)
 +    except svn_errors.InvalidPropertyValue:
 +        return None
 +    if not mapping.is_branch(bp) and not mapping.is_tag(bp):
 +        return None
++    return mapping.revision_id_foreign_to_bzr((uuid, bp, revnum))
 +
 +
 +class RevisionMetadataBranch(object):
 +    """Describes a Bazaar-like branch in a Subversion repository."""
 +
 +    def __init__(self, mapping):
 +        self._revs = []
 +        self.mapping = mapping
 +
 +    def consider_bzr_fileprops(self, revmeta):
 +        """Check whether bzr file properties should be analysed for 
 +        this revmeta.
 +        """
 +        i = self._revs.index(revmeta)
 +        for desc in reversed(self._revs[:i]):
 +            if desc.knows_fileprops():
 +                return (desc.estimate_bzr_fileprop_ancestors() > 0)
 +        # assume the worst
 +        return True
 +
 +    def consider_svk_fileprops(self, revmeta):
 +        """Check whether svk file propertise should be analysed for 
 +        this revmeta.
 +        """
 +        i = self._revs.index(revmeta)
 +        for desc in reversed(self._revs[:i]):
 +            if desc.knows_fileprops():
 +                return (desc.estimate_svk_fileprop_ancestors() > 0)
 +        # assume the worst
 +        return True
 +
 +    def get_lhs_parent(self, revmeta):
 +        i = self._revs.index(revmeta)
 +        try:
 +            return self._revs[i+1]
 +        except IndexError:
 +            return None
 +
 +    def append(self, revmeta):
 +        assert len(self._revs) == 0 or self._revs[-1].revnum > revmeta.revnum
 +        self._revs.append(revmeta)
 +
 +
 +class RevisionMetadataProvider(object):
 +
 +    def __init__(self, repository, cache, check_revprops):
 +        self._revmeta_cache = {}
 +        self.repository = repository
 +        self._get_fileprops_fn = self.repository.branchprop_list.get_properties
 +        self._log = repository._log
 +        self.check_revprops = check_revprops
 +        if cache:
 +            self._revmeta_cls = CachingRevisionMetadata
 +        else:
 +            self._revmeta_cls = RevisionMetadata
 +
 +    def get_revision(self, path, revnum, changes=None, revprops=None, changed_fileprops=None, 
 +                     fileprops=None, metabranch=None):
 +        assert isinstance(path, str)
 +        assert isinstance(revnum, int)
 +        if (path, revnum) in self._revmeta_cache:
 +            cached = self._revmeta_cache[path,revnum]
 +            if changes is not None:
 +                cached.paths = changes
 +            if cached._changed_fileprops is None:
 +                cached._changed_fileprops = changed_fileprops
 +            if cached._fileprops is None:
 +                cached._fileprops = fileprops
 +            return self._revmeta_cache[path,revnum]
 +
 +        ret = self._revmeta_cls(self.repository, self.check_revprops, self._get_fileprops_fn,
 +                               self._log, self.repository.uuid, path, revnum, changes, revprops, 
 +                               changed_fileprops=changed_fileprops, fileprops=fileprops,
 +                               metabranch=metabranch)
 +        self._revmeta_cache[path,revnum] = ret
 +        return ret
 +
 +    def iter_changes(self, branch_path, from_revnum, to_revnum, mapping=None, pb=None, limit=0):
 +        """Iterate over all revisions backwards.
 +        
 +        :return: iterator that returns tuples with branch path, 
 +            changed paths, revision number, changed file properties and 
 +        """
 +        assert isinstance(branch_path, str)
 +        assert mapping is None or mapping.is_branch(branch_path) or mapping.is_tag(branch_path), \
 +                "Mapping %r doesn't accept %s as branch or tag" % (mapping, branch_path)
 +        assert from_revnum >= to_revnum
 +
 +        bp = branch_path
 +        i = 0
 +
 +        # Limit can't be passed on directly to LogWalker.iter_changes() 
 +        # because we're skipping some revs
 +        # TODO: Rather than fetching everything if limit == 2, maybe just 
 +        # set specify an extra X revs just to be sure?
 +        for (paths, revnum, revprops) in self._log.iter_changes([branch_path], from_revnum, to_revnum, 
 +                                                                pb=pb):
 +            assert bp is not None
 +            next = changes.find_prev_location(paths, bp, revnum)
 +            assert revnum > 0 or bp == ""
 +            assert mapping is None or mapping.is_branch(bp) or mapping.is_tag(bp), "%r is not a valid path" % bp
 +
 +            if (next is not None and 
 +                not (mapping is None or mapping.is_branch(next[0]) or mapping.is_tag(next[0]))):
 +                # Make it look like the branch started here if the mapping 
 +                # doesn't support weird paths as branches
 +                # TODO: Make this quicker - it can be very slow for large repos.
 +                lazypaths = logwalker.lazy_dict(paths, full_paths, self._log.find_children, paths, bp, next[0], next[1])
 +                paths[bp] = ('A', None, -1)
 +
 +                yield (bp, lazypaths, revnum, revprops)
 +                return
 +                     
 +            if changes.changes_path(paths, bp, False):
 +                yield (bp, paths, revnum, revprops)
 +                i += 1
 +                if limit != 0 and limit == i:
 +                    break
 +
 +            if next is None:
 +                bp = None
 +            else:
 +                bp = next[0]
 +
 +    def get_mainline(self, branch_path, revnum, mapping, pb=None):
 +        return list(self.iter_reverse_branch_changes(branch_path, revnum, to_revnum=0, mapping=mapping, pb=pb))
 +
 +    def branch_prev_location(self, revmeta, mapping):
 +        iterator = self.iter_reverse_branch_changes(revmeta.branch_path, revmeta.revnum, to_revnum=0, mapping=mapping, limit=2)
 +        firstrevmeta = iterator.next()
 +        assert revmeta == firstrevmeta
 +        try:
 +            parentrevmeta = iterator.next()
 +            if (not mapping.is_branch(parentrevmeta.branch_path) and
 +                not mapping.is_tag(parentrevmeta.branch_path)):
 +                return None
 +            return parentrevmeta
 +        except StopIteration:
 +            return None
 +
 +    def iter_reverse_branch_changes(self, branch_path, from_revnum, to_revnum, 
 +                                    mapping=None, pb=None, limit=0):
 +        """Return all the changes that happened in a branch 
 +        until branch_path,revnum. 
 +
 +        :return: iterator that returns RevisionMetadata objects.
 +        """
 +        assert mapping is None or mapping.is_branch(branch_path) or mapping.is_tag(branch_path)
 +        history_iter = self.iter_changes(branch_path, from_revnum, to_revnum, 
 +                                         mapping, pb=pb, limit=limit)
 +        metabranch = RevisionMetadataBranch(mapping)
 +        prev = None
 +        # Always make sure there is one more revision in the metabranch
 +        # so the yielded rev can find its left hand side parent.
 +        for (bp, paths, revnum, revprops) in history_iter:
 +            ret = self.get_revision(bp, revnum, paths, revprops, metabranch=metabranch)
 +            metabranch.append(ret)
 +            if prev is not None:
 +                yield prev
 +            prev = ret
 +        if prev is not None:
 +            yield prev
 +
 +    def iter_all_changes(self, layout, mapping, from_revnum, to_revnum=0, project=None, pb=None):
 +        assert from_revnum >= to_revnum
 +        metabranches = {}
 +        if mapping is None:
 +            mapping_check_path = lambda x:True
 +        else:
 +            mapping_check_path = lambda x: mapping.is_branch(x) or mapping.is_tag(x)
 +        # Layout decides which ones to pick up
 +        # Mapping decides which ones to keep
 +        def get_metabranch(bp):
 +            if not bp in metabranches:
 +                metabranches[bp] = RevisionMetadataBranch(mapping)
 +            return metabranches[bp]
 +
 +        if project is not None:
 +            prefixes = layout.get_project_prefixes(project)
 +        else:
 +            prefixes = [""]
 +        unusual = set()
 +        for (paths, revnum, revprops) in self._log.iter_changes(prefixes, from_revnum, to_revnum, pb=pb):
 +            bps = {}
 +            if pb:
 +                pb.update("discovering revisions", revnum, from_revnum-revnum)
 +
 +            for p in sorted(paths):
 +                action = paths[p][0]
 +
 +                try:
 +                    (_, bp, ip) = layout.split_project_path(p, project)
 +                except errors.NotBranchError:
 +                    pass
 +                    for u in unusual:
 +                        if p.startswith("%s/" % u):
 +                            bps[u] = metabranches[u]
 +                else:
 +                    if action != 'D' or ip != "":
 +                        bps[bp] = get_metabranch(bp)
 +            
 +            # Apply renames and the like for the next round
 +            for new_name, old_name in changes.apply_reverse_changes(metabranches.keys(), paths):
 +                if new_name in unusual:
 +                    unusual.remove(new_name)
 +                if old_name is None: 
 +                    # didn't exist previously
 +                    del metabranches[new_name]
 +                else:
 +                    data = metabranches[new_name]
 +                    del metabranches[new_name]
 +                    if mapping_check_path(old_name):
 +                        metabranches[old_name] = data
 +                        if not layout.is_branch_or_tag(old_name, project):
 +                            unusual.add(old_name)
 +
 +            for bp in bps:
 +                revmeta = self.get_revision(bp, revnum, paths, revprops, metabranch=bps[bp])
 +                bps[bp].append(revmeta)
 +                yield revmeta
 +    
 +        # Make sure commit 0 is processed
 +        if to_revnum == 0 and layout.is_branch_or_tag("", project):
 +            bps[""] = get_metabranch("")
 +            yield self.get_revision("", 0, {"": ('A', None, -1)}, {}, metabranch=bps[""])
index 44922ea1f11e9f4e906081f1bf0512ab05133168,0000000000000000000000000000000000000000..77f2571a9be056dc4a133e207487d0c3ec4a44f5
mode 100644,000000..100644
--- /dev/null
@@@ -1,514 -1,0 +1,514 @@@
-                 mapping.revision_id_foreign_to_bzr((repos.uuid, 1, "")),
 +# 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.branch import Branch
 +from bzrlib.bzrdir import BzrDir
 +from bzrlib.errors import NoSuchRevision
 +from bzrlib.repository import Repository
 +from bzrlib.tests import TestCase
 +from bzrlib.trace import mutter
 +
 +from bzrlib.plugins.svn import format
 +from bzrlib.plugins.svn.layout import TrunkLayout, RootLayout
 +from bzrlib.plugins.svn.mapping import SVN_PROP_BZR_REVISION_ID, mapping_registry
 +from bzrlib.plugins.svn.mapping3 import BzrSvnMappingv3FileProps, SVN_PROP_BZR_BRANCHING_SCHEME, set_property_scheme
 +from bzrlib.plugins.svn.mapping3.scheme import NoBranchingScheme, ListBranchingScheme
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +from bzrlib.plugins.svn.tests.test_mapping import sha1
 +
 +class Mappingv3FilePropTests(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_file_id_spaces(self):
 +        self.assertFalse(" " in self.mapping.generate_file_id("uuid", 1, "b p", u"my path"))
 +
 +    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 RepositoryTests(SubversionTestCase):
 +
 +    def setUp(self):
 +        super(RepositoryTests, self).setUp()
 +        self.repos_url = self.make_repository("d")
 +        self._old_mapping = mapping_registry._get_default_key()
 +        mapping_registry.set_default("v3")
 +
 +    def tearDown(self):
 +        super(RepositoryTests, self).tearDown()
 +        mapping_registry.set_default("v3")
 +
 +    def test_generate_revision_id_forced_revid(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.change_prop(SVN_PROP_BZR_REVISION_ID+"v3-none", 
 +                             "2 someid\n")
 +        dc.close()
 +
 +        repos = Repository.open(self.repos_url)
 +        mapping = repos.get_mapping()
 +        if not mapping.roundtripping:
 +            raise TestNotApplicable()
 +        revid = repos.generate_revision_id(1, "", mapping)
 +        self.assertEquals("someid", revid)
 +
 +    def test_generate_revision_id_forced_revid_invalid(self):
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.change_prop(SVN_PROP_BZR_REVISION_ID+"v3-none", "corrupt-id\n")
 +        dc.close()
 +
 +        repos = Repository.open(self.repos_url)
 +        mapping = repos.get_mapping()
 +        revid = repos.generate_revision_id(1, "", mapping)
 +        self.assertEquals(
-         revid = mapping.revision_id_foreign_to_bzr((repository.uuid, 2, ""))
++                mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1)),
 +                revid)
 +
 +    def test_revision_ghost_parents(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_file("foo").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.open_file("foo").modify("data2")
 +        dc.change_prop("bzr:ancestry:v3-none", "ghostparent\n")
 +        dc.close()
 +
 +        repository = Repository.open(self.repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual((),
 +                repository.get_revision(
 +                    repository.generate_revision_id(0, "", mapping)).parent_ids)
 +        self.assertEqual((repository.generate_revision_id(0, "", mapping),),
 +                repository.get_revision(
 +                    repository.generate_revision_id(1, "", mapping)).parent_ids)
 +        self.assertEqual((repository.generate_revision_id(1, "", mapping),
 +            "ghostparent"), 
 +                repository.get_revision(
 +                    repository.generate_revision_id(2, "", mapping)).parent_ids)
 + 
 +    def test_get_revision_id_overriden(self):
 +        self.make_checkout(self.repos_url, 'dc')
 +        repository = Repository.open(self.repos_url)
 +        self.assertRaises(NoSuchRevision, repository.get_revision, "nonexisting")
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.build_tree({'dc/foo': "data2"})
 +        self.client_set_prop("dc", "bzr:revision-id:v3-none", 
 +                            "3 myrevid\n")
 +        self.client_update("dc")
 +        (num, date, author) = self.client_commit("dc", "Second Message")
 +        repository = Repository.open(self.repos_url)
 +        mapping = repository.get_mapping()
 +        if not mapping.roundtripping:
 +            raise TestNotApplicable
-             mapping.revision_id_foreign_to_bzr((repository.uuid, 1, "")))[:2])
++        revid = mapping.revision_id_foreign_to_bzr((repository.uuid, "", 2))
 +        rev = repository.get_revision("myrevid")
 +        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
 +                rev.parent_ids)
 +        self.assertEqual(rev.revision_id, 
 +                         repository.generate_revision_id(2, "", mapping))
 +        self.assertEqual(author, rev.committer)
 +        self.assertIsInstance(rev.properties, dict)
 +
 +    def test_get_ancestry_merged(self):
 +        self.make_checkout(self.repos_url, 'dc')
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.client_update("dc")
 +        self.client_set_prop("dc", "bzr:ancestry:v3-none", "a-parent\n")
 +        self.build_tree({'dc/foo': "data2"})
 +        self.client_commit("dc", "Second Message")
 +        repository = Repository.open(self.repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(0, "", mapping)))
 +        self.assertEqual([None, repository.generate_revision_id(0, "", mapping),
 +            repository.generate_revision_id(1, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(1, "", mapping)))
 +        self.assertEqual([None, 
 +            repository.generate_revision_id(0, "", mapping), "a-parent", 
 +            repository.generate_revision_id(1, "", mapping), 
 +                  repository.generate_revision_id(2, "", mapping)], 
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(2, "", mapping)))
 +
 +    def test_lookup_revision_id_overridden(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("bloe")
 +        dc.change_prop(SVN_PROP_BZR_REVISION_ID+"v3-none", "2 myid\n")
 +        dc.close()
 +        repository = Repository.open(self.repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual(("", 1), repository.lookup_revision_id( 
-             mapping.revision_id_foreign_to_bzr((repository.uuid, 1, "")))[:2])
++            mapping.revision_id_foreign_to_bzr((repository.uuid, "", 1)))[:2])
 +        self.assertEqual(("", 1), 
 +                repository.lookup_revision_id("myid")[:2])
 +
 +    def test_lookup_revision_id_overridden_invalid(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("bloe")
 +        dc.change_prop(SVN_PROP_BZR_REVISION_ID+"v3-none", "corrupt-entry\n")
 +        dc.close()
 +
 +        repository = Repository.open(self.repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual(("", 1), repository.lookup_revision_id( 
-             mapping.revision_id_foreign_to_bzr((repository.uuid, 2, "")))[:2])
++            mapping.revision_id_foreign_to_bzr((repository.uuid, "", 1)))[:2])
 +        self.assertRaises(NoSuchRevision, repository.lookup_revision_id, 
 +            "corrupt-entry")
 +
 +    def test_lookup_revision_id_overridden_invalid_dup(self):
 +        self.make_checkout(self.repos_url, 'dc')
 +        self.build_tree({'dc/bloe': None})
 +        self.client_add("dc/bloe")
 +        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"v3-none", 
 +                             "corrupt-entry\n")
 +        self.client_commit("dc", "foobar")
 +        self.build_tree({'dc/bla': None})
 +        self.client_add("dc/bla")
 +        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"v3-none", 
 +                "corrupt-entry\n2 corrupt-entry\n")
 +        self.client_commit("dc", "foobar")
 +        repository = Repository.open(self.repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual(("", 2), repository.lookup_revision_id( 
-             mapping.revision_id_foreign_to_bzr((repository.uuid, 1, "")))[:2])
++            mapping.revision_id_foreign_to_bzr((repository.uuid, "", 2)))[:2])
 +        self.assertEqual(("", 1), repository.lookup_revision_id( 
++            mapping.revision_id_foreign_to_bzr((repository.uuid, "", 1)))[:2])
 +        self.assertEqual(("", 2), repository.lookup_revision_id( 
 +            "corrupt-entry")[:2])
 +
 +    def test_lookup_revision_id_overridden_not_found(self):
 +        """Make sure a revision id that is looked up but doesn't exist 
 +        doesn't accidently end up in the revid cache."""
 +        self.make_checkout(self.repos_url, 'dc')
 +        self.build_tree({'dc/bloe': None})
 +        self.client_add("dc/bloe")
 +        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"v3-none", "2 myid\n")
 +        self.client_commit("dc", "foobar")
 +        repository = Repository.open(self.repos_url)
 +        self.assertRaises(NoSuchRevision, 
 +                repository.lookup_revision_id, "foobar")
 +
 +    def test_set_branching_scheme_property(self):
 +        self.make_checkout(self.repos_url, 'dc')
 +        self.client_set_prop("dc", SVN_PROP_BZR_BRANCHING_SCHEME, 
 +            "trunk\nbranches/*\nbranches/tmp/*")
 +        self.client_commit("dc", "set scheme")
 +        repository = Repository.open(self.repos_url)
 +        self.assertEquals(ListBranchingScheme(["trunk", "branches/*", "branches/tmp/*"]).branch_list,
 +                          repository.get_mapping().scheme.branch_list)
 +
 +    def test_set_property_scheme(self):
 +        self.make_checkout(self.repos_url, 'dc')
 +        repos = Repository.open(self.repos_url)
 +        set_property_scheme(repos, ListBranchingScheme(["bla/*"]))
 +        self.client_update("dc")
 +        self.assertEquals("bla/*\n", 
 +                   self.client_get_prop("dc", SVN_PROP_BZR_BRANCHING_SCHEME))
 +        self.assertEquals("Updating branching scheme for Bazaar.", 
 +                self.client_log(self.repos_url, 1, 1)[1][3])
 +
 +    def test_fetch_fileid_renames(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_file("test").modify("data")
 +        dc.change_prop("bzr:file-ids", "test\tbla\n")
 +        dc.change_prop("bzr:revision-info", "")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        dir = BzrDir.create("f", format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        mapping = oldrepos.get_mapping()
 +        self.assertEqual("bla", newrepos.get_inventory(
 +            oldrepos.generate_revision_id(1, "", mapping)).path2id("test"))
 +
 +    def test_fetch_ghosts(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_file("bla").modify("data")
 +        dc.change_prop("bzr:ancestry:v3-none", "aghost\n")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        oldrepos.set_layout(RootLayout())
 +        dir = BzrDir.create("f", format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        mapping = oldrepos.get_mapping()
 +
 +        rev = newrepos.get_revision(oldrepos.generate_revision_id(1, "", mapping))
 +        self.assertTrue("aghost" in rev.parent_ids)
 +
 +    def test_fetch_invalid_ghosts(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_file("bla").modify("data")
 +        dc.change_prop("bzr:ancestry:v3-none", "a ghost\n")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        dir = BzrDir.create("f", format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        
 +        mapping = oldrepos.get_mapping()
 +
 +        rev = newrepos.get_revision(oldrepos.generate_revision_id(1, "", mapping))
 +        self.assertEqual([oldrepos.generate_revision_id(0, "", mapping)], rev.parent_ids)
 +
 +    def test_fetch_complex_ids_dirs(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dir = dc.add_dir("dir")
 +        dir.add_dir("dir/adir")
 +        dc.change_prop("bzr:revision-info", "")
 +        dc.change_prop("bzr:file-ids", "dir\tbloe\ndir/adir\tbla\n")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_dir("bdir", "dir/adir")
 +        dir = dc.open_dir("dir")
 +        dir.delete("dir/adir")
 +        dc.change_prop("bzr:revision-info", "properties: \n")
 +        dc.change_prop("bzr:file-ids", "bdir\tbla\n")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        dir = BzrDir.create("f", format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        mapping = oldrepos.get_mapping()
 +        tree = newrepos.revision_tree(oldrepos.generate_revision_id(2, "", mapping))
 +        self.assertEquals("bloe", tree.path2id("dir"))
 +        self.assertIs(None, tree.path2id("dir/adir"))
 +        self.assertEquals("bla", tree.path2id("bdir"))
 +
 +    def test_fetch_complex_ids_files(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dir = dc.add_dir("dir")
 +        dir.add_file("dir/adir").modify("contents")
 +        dc.change_prop("bzr:revision-info", "")
 +        dc.change_prop("bzr:file-ids", "dir\tbloe\ndir/adir\tbla\n")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_file("bdir", "dir/adir")
 +        dir = dc.open_dir("dir")
 +        dir.delete("dir/adir")
 +        dc.change_prop("bzr:revision-info", "properties: \n")
 +        dc.change_prop("bzr:file-ids", "bdir\tbla\n")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        dir = BzrDir.create("f", format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        mapping = oldrepos.get_mapping()
 +        tree = newrepos.revision_tree(oldrepos.generate_revision_id(2, "", mapping))
 +        self.assertEquals("bloe", tree.path2id("dir"))
 +        self.assertIs(None, tree.path2id("dir/adir"))
 +        mutter('entries: %r' % tree.inventory.entries())
 +        self.assertEquals("bla", tree.path2id("bdir"))
 +
 +    def test_store_branching_scheme(self):
 +        self.make_checkout(self.repos_url, 'dc')
 +        repository = Repository.open(self.repos_url)
 +        repository.set_layout(TrunkLayout(42))
 +        repository = Repository.open(self.repos_url)
 +        self.assertEquals("trunk42", str(repository.get_mapping().scheme))
 +
 +    def test_revision_fileidmap(self):
 +        dc = self.get_commit_editor(self.repos_url)
 +        dc.add_file("foo").modify("data")
 +        dc.change_prop("bzr:revision-info", "")
 +        dc.change_prop("bzr:file-ids", "foo\tsomeid\n")
 +        dc.close()
 +
 +        repository = Repository.open(self.repos_url)
 +        repository.set_layout(RootLayout())
 +        tree = repository.revision_tree(Branch.open(self.repos_url).last_revision())
 +        self.assertEqual("someid", tree.inventory.path2id("foo"))
 +        self.assertFalse("1@%s::foo" % repository.uuid in tree.inventory)
 +
 +    def test_commit_revision_id(self):
 +        self.make_checkout(self.repos_url, "dc")
 +        wt = WorkingTree.open("dc")
 +        self.build_tree({'dc/foo/bla': "data", 'dc/bla': "otherdata"})
 +        wt.add('bla')
 +        wt.commit(message="data")
 +
 +        branch = Branch.open(self.repos_url)
 +        builder = branch.get_commit_builder([branch.last_revision()], 
 +                revision_id="my-revision-id")
 +        tree = branch.repository.revision_tree(branch.last_revision())
 +        new_tree = copy(tree)
 +        ie = new_tree.inventory.root
 +        ie.revision = None
 +        builder.record_entry_contents(ie, [tree.inventory], '', new_tree, 
 +                                      None)
 +        builder.finish_inventory()
 +        builder.commit("foo")
 +
 +        self.assertEqual("3 my-revision-id\n", 
 +            self.client_get_prop("dc", 
 +                "bzr:revision-id:v3-none", 2))
 +
 +    def test_commit_metadata(self):
 +        self.make_checkout(self.repos_url, "dc")
 +
 +        wt = WorkingTree.open("dc")
 +        self.build_tree({'dc/foo/bla': "data", 'dc/bla': "otherdata"})
 +        wt.add('bla')
 +        wt.commit(message="data")
 +
 +        branch = Branch.open(self.repos_url)
 +        builder = branch.get_commit_builder([branch.last_revision()], 
 +                timestamp=4534.0, timezone=2, committer="fry",
 +                revision_id="my-revision-id")
 +        tree = branch.repository.revision_tree(branch.last_revision())
 +        new_tree = copy(tree)
 +        ie = new_tree.inventory.root
 +        ie.revision = None
 +        builder.record_entry_contents(ie, [tree.inventory], '', new_tree, None)
 +        builder.finish_inventory()
 +        builder.commit("foo")
 +
 +        self.assertEqual("3 my-revision-id\n", 
 +                self.client_get_prop("dc", "bzr:revision-id:v3-none", 2))
 +
 +        self.assertEqual(
 +                "timestamp: 1970-01-01 01:15:36.000000000 +0000\ncommitter: fry\n",
 +                self.client_get_prop("dc", "bzr:revision-info", 2))
 +
 +    def test_commit_parents(self):
 +        self.make_checkout(self.repos_url, "dc")
 +        self.build_tree({'dc/foo/bla': "data"})
 +        self.client_add("dc/foo")
 +        wt = WorkingTree.open("dc")
 +        wt.set_pending_merges(["some-ghost-revision"])
 +        self.assertEqual(["some-ghost-revision"], wt.get_parent_ids()[1:])
 +        wt.commit(message="data")
 +        self.assertEqual("some-ghost-revision\n", 
 +                self.client_get_prop(self.repos_url, "bzr:ancestry:v3-none", 1))
 +        self.assertEqual((wt.branch.generate_revision_id(0), "some-ghost-revision"),
 +                         wt.branch.repository.get_revision(
 +                             wt.branch.last_revision()).parent_ids)
 +
 +    def test_push_unnecessary_merge(self):        
 +        from bzrlib.debug import debug_flags
 +        debug_flags.add("commit")
 +        debug_flags.add("fetch")
 +        repos_url = self.make_repository("a")
 +        bzrwt = BzrDir.create_standalone_workingtree("c", 
 +            format=format.get_rich_root_format())
 +        self.build_tree({'c/registry/generic.c': "Tour"})
 +        bzrwt.add("registry")
 +        bzrwt.add("registry/generic.c")
 +        revid1 = bzrwt.commit("Add initial directory + file", 
 +                              rev_id="initialrevid")
 +
 +        # Push first branch into Subversion
 +        newdir = BzrDir.open(repos_url+"/trunk")
 +        newbranch = newdir.import_branch(bzrwt.branch)
 +
 +        c = ra.RemoteAccess(repos_url)
 +        self.assertTrue(c.check_path("trunk/registry/generic.c", c.get_latest_revnum()) == core.NODE_FILE)
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.open_dir("trunk")
 +        registry = trunk.open_dir("trunk/registry")
 +        registry.open_file("trunk/registry/generic.c").modify("BLA")
 +        dc.close()
 +        mapping = newdir.find_repository().get_mapping()
 +        merge_revid = newdir.find_repository().generate_revision_id(2, "trunk", mapping)
 +
 +        # Merge 
 +        self.build_tree({'c/registry/generic.c': "DE"})
 +        bzrwt.add_pending_merge(merge_revid)
 +        self.assertEquals(bzrwt.get_parent_ids()[1], merge_revid)
 +        revid2 = bzrwt.commit("Merge something", rev_id="mergerevid")
 +        bzr_parents = bzrwt.branch.repository.get_revision(revid2).parent_ids
 +        trunk = Branch.open(repos_url + "/trunk")
 +        trunk.pull(bzrwt.branch)
 +
 +        self.assertEquals(tuple(bzr_parents), 
 +                trunk.repository.get_revision(revid2).parent_ids)
 +
 +        self.assertEquals([revid1, revid2], trunk.revision_history())
 +        self.assertEquals(
 +                '1 initialrevid\n2 mergerevid\n',
 +                self.client_get_prop(repos_url+"/trunk", SVN_PROP_BZR_REVISION_ID+"v3-trunk0",
 +                                     c.get_latest_revnum()))
index 99d66accb89fe9af0286b72a335edda4089ae2ae,0000000000000000000000000000000000000000..9e4474d9a3cd2d7407a1be5e1d7122e0f04204c1
mode 100644,000000..100644
--- /dev/null
@@@ -1,132 -1,0 +1,132 @@@
-         (uuid, path, revnum, mapping) = self.mapping.revision_id_bzr_to_foreign(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.errors import InvalidRevisionId
 +from bzrlib.revision import Revision
 +from bzrlib.tests import TestCase, TestNotApplicable
 +
 +from bzrlib.plugins.svn.mapping import mapping_registry
 +
 +class RoundtripMappingTests(TestCase):
 +
 +    def setUp(self):
 +        super(RoundtripMappingTests, self).setUp()
 +        self.mapping = mapping_registry.get(self.mapping_name).get_test_instance()
 +
 +    def test_roundtrip_revision(self):
 +        revid = self.mapping.revision_id_foreign_to_bzr(("myuuid", 42, "path"))
-         self.assertEquals(("myuuid", "bla", 5, self.mapping), 
++        (uuid, path, revnum), mapping = self.mapping.revision_id_bzr_to_foreign(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.roundtripping:
 +            raise TestNotApplicable
 +        fileids = {"": "some-id", "bla/blie": "other-id"}
 +        revprops = {}
 +        fileprops = {}
 +        self.mapping.export_revision("branchp", 432432432.0, 0, "somebody", {}, "arevid", 4, ["merge1"], revprops, fileprops)
 +        self.mapping.export_fileid_map(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.roundtripping:
 +            raise TestNotApplicable
 +        revprops = {}
 +        fileprops = {}
 +        text_parents = {"bla": ["bloe"], "ll": ["12", "bli"]}
 +        self.mapping.export_text_parents(text_parents, revprops, fileprops)
 +        self.assertEquals(text_parents,
 +            self.mapping.import_text_parents(revprops, fileprops))
 +
 +    def test_text_revisions(self):
 +        if not self.mapping.roundtripping:
 +            raise TestNotApplicable
 +        revprops = {}
 +        fileprops = {}
 +        text_revisions = {"bla": "bloe", "ll": "12"}
 +        self.mapping.export_text_revisions(text_revisions, revprops, fileprops)
 +        self.assertEquals(text_revisions,
 +            self.mapping.import_text_revisions(revprops, fileprops))
 +
 +    def test_message(self):
 +        if not self.mapping.roundtripping:
 +            raise TestNotApplicable
 +        revprops = {}
 +        fileprops = {}
 +        self.mapping.export_revision("branchp", 432432432.0, 0, "somebody", 
 +                                     {"arevprop": "val"}, "arevid", 4, ["merge1"], revprops, fileprops)
 +        revprops["svn:date"] = "2008-11-03T09:33:00.716938Z"
 +        try:
 +            self.mapping.export_message("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.roundtripping:
 +            raise TestNotApplicable
 +        revprops = {}
 +        fileprops = {}
 +        self.mapping.export_revision("branchp", 432432432.0, 0, "somebody", 
 +                                     {"arevprop": "val" }, "arevid", 4, ["parent", "merge1"], revprops, fileprops)
 +        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.roundtripping:
 +            raise TestNotApplicable
 +        revprops = {}
 +        fileprops = {}
 +        self.mapping.export_revision("branchp", 432432432.0, 0, "somebody", {}, "arevid", 4, ["parent", "merge1"], revprops, fileprops)
 +        self.assertEquals((4, "arevid"), self.mapping.get_revision_id("branchp", revprops, fileprops))
 +    
 +    def test_revision_id_none(self):
 +        if not self.mapping.roundtripping:
 +            raise TestNotApplicable
 +        self.assertEquals((None, None), self.mapping.get_revision_id("bp", {}, dict()))
 +
 +    def test_parse_revision_id_unknown(self):
 +        self.assertRaises(InvalidRevisionId, 
 +                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"))))
 +
 +
 +    def test_import_revision_svnprops(self):
 +        rev = Revision(None)
 +        self.mapping.import_revision({"svn:log": "A log msg",
 +                                      "svn:author": "Somebody",
 +                                      "svn:date": "2008-11-03T09:33:00.716938Z"}, {}, "someuuid", "trunk", 23, rev)
 +        self.assertEquals("Somebody", rev.committer)
 +        self.assertEquals("A log msg", rev.message)
 +        self.assertEquals({}, rev.properties)
 +        self.assertEquals(1225704780.716938, rev.timestamp)
 +        self.assertEquals(0.0, rev.timezone)
 +
index e6bfe944e3fd3b87e525cd1362d0e18e7a7bdf1a,0000000000000000000000000000000000000000..fabc113c74cd1d74d6ea6ac93a4d05687a494b5c
mode 100644,000000..100644
--- /dev/null
@@@ -1,77 -1,0 +1,77 @@@
- from bzrlib.plugins.svn.mapping import SVN_PROP_BZR_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/>.
 +
 +"""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
-         super(TestSubversionMappingRepositoryWorks, self).setUp()
++from bzrlib.plugins.svn.mapping import SVN_PROP_BZR_REVISION_ID, mapping_registry
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +
 +class WorkingSubversionBranch(SubversionTestCase):
 +
 +    def setUp(self):
-         super(TestSubversionMappingRepositoryWorks, self).tearDown()
++        super(WorkingSubversionBranch, self).setUp()
 +        self._old_mapping = mapping_registry._get_default_key()
 +        mapping_registry.set_default(self.mapping_name)
 +
 +    def tearDown(self):
++        super(WorkingSubversionBranch, self).tearDown()
 +        mapping_registry.set_default(self._old_mapping)
 +
 +    def test_revision_id_to_revno_simple(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify()
 +        dc.change_prop("bzr:revision-id:v3-none", 
 +                            "2 myrevid\n")
 +        dc.close()
 +
 +        branch = Branch.open(repos_url)
 +        self.assertEquals(2, branch.revision_id_to_revno("myrevid"))
 +
 +    def test_revision_id_to_revno_older(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("foo").modify()
 +        dc.change_prop("bzr:revision-id:v3-none", 
 +                            "2 myrevid\n")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("foo").modify()
 +        dc.change_prop("bzr:revision-id:v3-none", 
 +                            "2 myrevid\n3 mysecondrevid\n")
 +        dc.close()
 +
 +        branch = Branch.open(repos_url)
 +        self.assertEquals(3, branch.revision_id_to_revno("mysecondrevid"))
 +        self.assertEquals(2, branch.revision_id_to_revno("myrevid"))
 +
 +
index 1a1dc77e77cdd1a2294aaf6e8f20ffcac2d7a61d,0000000000000000000000000000000000000000..c4462c30bfecf3abadb3af3b284044421fd4e034
mode 100644,000000..100644
--- /dev/null
@@@ -1,1038 -1,0 +1,1038 @@@
-         self.assertEqual({u"": (mapping.generate_file_id(repos.uuid, 0, "", u""), mapping.revision_id_foreign_to_bzr((repos.uuid, 0, "")))}, repos.get_fileid_map(0, "", mapping))
 +# 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 urlutils
 +from bzrlib.branch import Branch
 +from bzrlib.bzrdir import BzrDir, format_registry
 +from bzrlib.config import GlobalConfig
 +from bzrlib.errors import NoSuchRevision, UninitializableFormat
 +from bzrlib.inventory import Inventory
 +from bzrlib.osutils import has_symlinks
 +from bzrlib.repository import Repository
 +from bzrlib.revision import NULL_REVISION
 +from bzrlib.tests import TestCase, TestSkipped, TestNotApplicable
 +
 +from bzrlib.plugins.svn import errors as svn_errors, format, ra
 +from bzrlib.plugins.svn.layout import TrunkLayout, RootLayout, CustomLayout
 +from bzrlib.plugins.svn.mapping import mapping_registry
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +
 +
 +class TestSubversionMappingRepositoryWorks(SubversionTestCase):
 +    """Mapping-dependent tests for Subversion repositories."""
 +
 +    def setUp(self):
 +        super(TestSubversionMappingRepositoryWorks, self).setUp()
 +        self._old_mapping = mapping_registry._get_default_key()
 +        mapping_registry.set_default(self.mapping_name)
 +
 +    def tearDown(self):
 +        super(TestSubversionMappingRepositoryWorks, self).tearDown()
 +        mapping_registry.set_default(self._old_mapping)
 +
 +    def test_get_branch_log(self):
 +        repos_url = self.make_repository("a")
 +        cb = self.get_commit_editor(repos_url)
 +        cb.add_file("foo").modify()
 +        cb.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +
 +        self.assertEqual([
 +            ('', {'foo': ('A', None, -1)}, 1), 
 +            ('', {'': ('A', None, -1)}, 0)],
 +            [(l.branch_path, l.get_paths(), l.revnum) for l in repos._revmeta_provider.iter_reverse_branch_changes("", 1, 0, repos.get_mapping())])
 +
 +    def test_iter_changes_parent_rename(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        foo = dc.add_dir("foo")
 +        foo.add_dir("foo/bar")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("bla", "foo")
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        try:
 +            repos.set_layout(CustomLayout(["bla/bar"]))
 +        except svn_errors.LayoutUnusable:
 +            raise TestNotApplicable
 +        ret = list(repos._revmeta_provider.iter_changes('bla/bar', 2, 0, repos.get_mapping()))
 +        self.assertEquals(1, len(ret))
 +        if repos.get_mapping().is_branch("foo/bar"):
 +            self.assertEquals("foo/bar", ret[0][0])
 +        else:
 +            self.assertEquals("bla/bar", ret[0][0])
 +
 +    def test_set_make_working_trees(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        repos.set_make_working_trees(True)
 +        self.assertFalse(repos.make_working_trees())
 +
 +    def test_get_fileid_map(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        mapping = repos.get_mapping()
-         revid = repos.get_mapping().revision_id_foreign_to_bzr((repos.uuid, 1, ""))
++        self.assertEqual({u"": (mapping.generate_file_id(repos.uuid, 0, "", u""), mapping.revision_id_foreign_to_bzr((repos.uuid, "", 0)))}, repos.get_fileid_map(0, "", mapping))
 +
 +    def test_add_revision(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        self.assertRaises(NotImplementedError, repos.add_revision, "revid", 
 +                None)
 +
 +    def test_has_signature_for_revision_id_no(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        self.assertFalse(repos.has_signature_for_revision_id("foo"))
 +
 +    def test_set_signature(self):
 +        repos_url = self.make_client("a", "dc")
 +        repos = Repository.open(repos_url)
 +        cb = self.get_commit_editor(repos_url)
 +        cb.add_file("foo").modify("bar")
 +        cb.close()
-             repository.get_mapping().revision_id_foreign_to_bzr((repository.uuid, 5, ""))))
++        revid = repos.get_mapping().revision_id_foreign_to_bzr((repos.uuid, "", 1))
 +        repos.add_signature_text(revid, "TEXT")
 +        self.assertTrue(repos.has_signature_for_revision_id(revid))
 +        self.assertEquals(repos.get_signature_text(revid), "TEXT")
 +
 +    def test_get_branch_invalid_revision(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +        self.assertRaises(NoSuchRevision, list, 
 +               repos._revmeta_provider.iter_reverse_branch_changes("/", 20, 0))
 +
 +    def test_follow_branch_switched_parents(self):
 +        repos_url = self.make_client('a', 'dc')
 +        self.build_tree({'dc/pykleur/trunk/pykleur': None})
 +        self.client_add("dc/pykleur")
 +        self.client_commit("dc", "initial")
 +        self.build_tree({'dc/pykleur/trunk/pykleur/afile': 'contents'})
 +        self.client_add("dc/pykleur/trunk/pykleur/afile")
 +        self.client_commit("dc", "add file")
 +        self.client_copy("dc/pykleur", "dc/pygments", 1)
 +        self.client_delete('dc/pykleur')
 +        self.client_update("dc")
 +        self.client_commit("dc", "commit")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(1))
 +        results = [(l.branch_path, l.get_paths(), l.revnum) for l in repos._revmeta_provider.iter_reverse_branch_changes("pygments/trunk", 3, 0)]
 +
 +        # Results differ per Subversion version, yay
 +        # For <= 1.4:
 +        if ra.version()[1] <= 4:
 +            self.assertEquals([
 +            ('pygments/trunk', {'pygments': (u'A', 'pykleur', 1),
 +                                'pygments/trunk': (u'R', 'pykleur/trunk', 2),
 +                                'pykleur': (u'D', None, -1)}, 3),
 +            ('pykleur/trunk', {'pykleur/trunk/pykleur/afile': (u'A', None, -1)}, 2),
 +            ('pykleur/trunk',
 +                    {'pykleur': (u'A', None, -1),
 +                     'pykleur/trunk': (u'A', None, -1),
 +                     'pykleur/trunk/pykleur': (u'A', None, -1)},
 +             1)], results
 +            )
 +        else:
 +            self.assertEquals(
 +               [
 +                ('pykleur/trunk', {'pykleur': (u'A', None, -1), 
 +                                   'pykleur/trunk': (u'A', None, -1), 
 +                                   'pykleur/trunk/pykleur': (u'A', None, -1)}, 
 +                1)], results
 +            )
 +
 +    def test_follow_branch_move_single(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        pykleur = dc.add_dir("pykleur")
 +        pykleur.add_dir("pykleur/bla")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("pygments", "pykleur", 1)
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        try:
 +            repos.set_layout(CustomLayout(["pygments"]))
 +        except svn_errors.LayoutUnusable:
 +            raise TestNotApplicable
 +        changes = repos._revmeta_provider.iter_reverse_branch_changes("pygments", 2, 0, mapping=repos.get_mapping())
 +        if repos.get_mapping().is_branch("pykleur"):
 +            self.assertEquals([('pygments',
 +                  {'pygments': ('A', 'pykleur', 1)},
 +                    2),
 +                    ('pykleur', {'pykleur': ('A', None, -1), 'pykleur/bla': ('A', None, -1)}, 1)],
 +                    [(l.branch_path, l.get_paths(), l.revnum) for l in changes])
 +        else:
 +            self.assertEquals([('pygments',
 +              {'pygments/bla': ('A', None, -1), 'pygments': ('A', None, -1)},
 +                2)],
 +                [(l.branch_path, l.get_paths(), l.revnum) for l in changes])
 +
 +    def test_history_all(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/file").modify("data")
 +        foo = dc.add_dir("foo")
 +        foo.add_file("foo/file").modify("data")
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +
 +        self.assertEqual(1, 
 +                   len(set(repos.all_revision_ids(TrunkLayout()))))
 +
 +    def test_all_revs_empty(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        self.assertEqual(set([]), set(repos.all_revision_ids()))
 +
 +    def test_all_revs(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/file").modify("data")
 +        foo = dc.add_dir("foo")
 +        foo.add_file("foo/file").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.add_dir("branches")
 +        somebranch = branches.add_dir("branches/somebranch")
 +        somebranch.add_file("branches/somebranch/somefile").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.open_dir("branches")
 +        branches.delete("branches/somebranch")
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        mapping = repos.get_mapping()
 +        self.assertEqual(set([
 +            repos.generate_revision_id(1, "trunk", mapping), 
 +            repos.generate_revision_id(2, "branches/somebranch", mapping)]),
 +            set(repos.all_revision_ids()))
 +
 +    def test_follow_history_empty(self):
 +        repos_url = self.make_repository("a")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +        self.assertEqual(set([repos.generate_revision_id(0, '', repos.get_mapping())]), 
 +              set(repos.all_revision_ids(repos.get_layout())))
 +
 +    def test_follow_history_empty_branch(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/afile").modify("data")
 +        dc.add_dir("branches")
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        self.assertEqual(set([repos.generate_revision_id(1, 'trunk', repos.get_mapping())]), 
 +                set(repos.all_revision_ids(repos.get_layout())))
 +
 +    def test_follow_history_follow(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/afile").modify("data")
 +        dc.add_dir("branches")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.open_dir("branches")
 +        branches.add_dir("branches/abranch", "trunk", 1)
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        items = set(repos.all_revision_ids(repos.get_layout()))
 +        self.assertEqual(set([repos.generate_revision_id(1, 'trunk', repos.get_mapping()),
 +                          repos.generate_revision_id(2, 'branches/abranch', repos.get_mapping())
 +                          ]), items)
 +
 +    def test_branch_log_specific(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({
 +            'dc/branches': None,
 +            'dc/branches/brancha': None,
 +            'dc/branches/branchab': None,
 +            'dc/branches/brancha/data': "data", 
 +            "dc/branches/branchab/data":"data"})
 +        self.client_add("dc/branches")
 +        self.client_commit("dc", "My Message")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        self.assertEqual(1, len(list(repos._revmeta_provider.iter_reverse_branch_changes("branches/brancha",
 +            1, 0))))
 +
 +    def test_branch_log_specific_ignore(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({'dc/branches': None})
 +        self.client_add("dc/branches")
 +        self.build_tree({
 +            'dc/branches/brancha': None,
 +            'dc/branches/branchab': None,
 +            'dc/branches/brancha/data': "data", 
 +            "dc/branches/branchab/data":"data"})
 +        self.client_add("dc/branches/brancha")
 +        self.client_commit("dc", "My Message")
 +
 +        self.client_add("dc/branches/branchab")
 +        self.client_commit("dc", "My Message2")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        self.assertEqual(1, len(list(repos._revmeta_provider.iter_reverse_branch_changes("branches/brancha",
 +            2, 0))))
 +
 +    def test_find_branches(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({
 +            'dc/branches/brancha': None,
 +            'dc/branches/branchab': None,
 +            'dc/branches/brancha/data': "data", 
 +            "dc/branches/branchab/data":"data"})
 +        self.client_add("dc/branches")
 +        self.client_commit("dc", "My Message")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        branches = repos.find_branches()
 +        self.assertEquals(2, len(branches))
 +        self.assertEquals(urlutils.join(repos.base, "branches/brancha"), 
 +                          branches[1].base)
 +        self.assertEquals(urlutils.join(repos.base, "branches/branchab"), 
 +                          branches[0].base)
 +
 +    def test_find_tags(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.add_dir("tags")
 +        tags.add_dir("tags/brancha").add_file("tags/brancha/data").modify()
 +        tags.add_dir("tags/branchab").add_file("tags/branchab/data").modify()
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        tags = repos.find_tags("")
 +        self.assertEquals({"brancha": repos.generate_revision_id(1, "tags/brancha", repos.get_mapping()),
 +                           "branchab": repos.generate_revision_id(1, "tags/branchab", repos.get_mapping())}, tags)
 +
 +    def test_find_tags_unmodified(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk").add_file("trunk/data").modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.add_dir("tags")
 +        tags.add_dir("tags/brancha", "trunk")
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        tags = repos.find_tags("")
 +        self.assertEquals({"brancha": repos.generate_revision_id(1, "trunk", repos.get_mapping())}, tags)
 +
 +    def test_find_tags_modified(self):
 +        repos_url = self.make_repository('a')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_dir("trunk").add_file("trunk/data").modify()
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.add_dir("tags")
 +        tags.add_dir("tags/brancha", "trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        tags = dc.open_dir("tags")
 +        brancha = tags.open_dir("tags/brancha")
 +        brancha.add_file("tags/brancha/release-notes").modify()
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        tags = repos.find_tags("")
 +        self.assertEquals({"brancha": repos.generate_revision_id(3, "tags/brancha", repos.get_mapping())}, tags)
 +
 +    def test_find_branchpaths_moved(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({
 +            'dc/tmp/branches/brancha': None,
 +            'dc/tmp/branches/branchab': None,
 +            'dc/tmp/branches/brancha/data': "data", 
 +            "dc/tmp/branches/branchab/data":"data"})
 +        self.client_add("dc/tmp")
 +        self.client_commit("dc", "My Message")
 +        self.client_copy("dc/tmp/branches", "dc/tags")
 +        self.client_commit("dc", "My Message 2")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        self.assertEqual([("tags/branchab", 2, True), 
 +                          ("tags/brancha", 2, True)], 
 +                list(repos.find_branchpaths(TrunkLayout(0), to_revnum=2)))
 +
 +    def test_find_branchpaths_start_revno(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.add_dir("branches")
 +        branches.add_dir("branches/brancha")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        branches = dc.open_dir("branches")
 +        branches.add_dir("branches/branchb")
 +        dc.close()
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        self.assertEqual([("branches/branchb", 2, True)],
 +                list(repos.find_branchpaths(TrunkLayout(0), from_revnum=2, to_revnum=2)))
 +
 +    def test_find_branchpaths_file_moved_from_nobranch(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({
 +            'dc/tmp/trunk': None,
 +            'dc/bla/somefile': "contents"})
 +        self.client_add("dc/tmp")
 +        self.client_add("dc/bla")
 +        self.client_commit("dc", "My Message")
 +        self.client_copy("dc/bla", "dc/tmp/branches")
 +        self.client_delete("dc/tmp/branches/somefile")
 +        self.client_commit("dc", "My Message 2")
 +
 +        Repository.open(repos_url).find_branchpaths(TrunkLayout(2))
 +
 +    def test_find_branchpaths_deleted_from_nobranch(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({
 +            'dc/tmp/trunk': None,
 +            'dc/bla/somefile': "contents"})
 +        self.client_add("dc/tmp")
 +        self.client_add("dc/bla")
 +        self.client_commit("dc", "My Message")
 +        self.client_copy("dc/bla", "dc/tmp/branches")
 +        self.client_delete("dc/tmp/branches/somefile")
 +        self.client_commit("dc", "My Message 2")
 +
 +        Repository.open(repos_url).find_branchpaths(TrunkLayout(1))
 +
 +    def test_find_branchpaths_moved_nobranch(self):
 +        repos_url = self.make_client("a", "dc")
 +        self.build_tree({
 +            'dc/tmp/nested/foobar': None,
 +            'dc/tmp/nested/branches/brancha': None,
 +            'dc/tmp/nested/branches/branchab': None,
 +            'dc/tmp/nested/branches/brancha/data': "data", 
 +            "dc/tmp/nested/branches/branchab/data":"data"})
 +        self.client_add("dc/tmp")
 +        self.client_commit("dc", "My Message")
 +        self.client_copy("dc/tmp/nested", "dc/t2")
 +        self.client_commit("dc", "My Message 2")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(1))
 +
 +        self.assertEqual([("t2/branches/brancha", 2, True), 
 +                          ("t2/branches/branchab", 2, True)], 
 +                list(repos.find_branchpaths(TrunkLayout(1), to_revnum=2)))
 +
 +    def test_find_branchpaths_root(self):
 +        repos_url = self.make_repository("a")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +
 +        self.assertEqual([("", 0, True)], 
 +                list(repos.find_branchpaths(RootLayout(), to_revnum=0)))
 +
 +    def test_find_branchpaths_no_later(self):
 +        repos_url = self.make_repository("a")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +
 +        self.assertEqual([("", 0, True)], 
 +                list(repos.find_branchpaths(RootLayout(), to_revnum=0)))
 +
 +    def test_find_branchpaths_trunk_empty(self):
 +        repos_url = self.make_repository("a")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        self.assertEqual([], 
 +                list(repos.find_branchpaths(TrunkLayout(0), to_revnum=0)))
 +
 +    def test_find_branchpaths_trunk_one(self):
 +        repos_url = self.make_repository("a")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/foo").modify("data")
 +        dc.close()
 +
 +        self.assertEqual([("trunk", 1, True)], 
 +                list(repos.find_branchpaths(TrunkLayout(0), to_revnum=1)))
 +
 +    def test_find_branchpaths_removed(self):
 +        repos_url = self.make_repository("a")
 +
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/foo").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.delete("trunk")
 +        dc.close()
 +
 +        self.assertEqual([("trunk", 1, True)], 
 +                list(repos.find_branchpaths(TrunkLayout(0), to_revnum=1)))
 +        self.assertEqual([("trunk", 1, False)], 
 +                list(repos.find_branchpaths(TrunkLayout(0), to_revnum=2)))
 +
 +    def test_has_revision(self):
 +        bzrdir = self.make_client_and_bzrdir('d', 'dc')
 +        repository = bzrdir.find_repository()
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.assertTrue(repository.has_revision(
 +            repository.generate_revision_id(1, "", repository.get_mapping())))
 +        self.assertFalse(repository.has_revision("some-other-revision"))
 +
 +    def test_has_revision_none(self):
 +        bzrdir = self.make_client_and_bzrdir('d', 'dc')
 +        repository = bzrdir.find_repository()
 +        self.assertTrue(repository.has_revision(None))
 +
 +    def test_has_revision_future(self):
 +        bzrdir = self.make_client_and_bzrdir('d', 'dc')
 +        repository = bzrdir.find_repository()
 +        self.assertFalse(repository.has_revision(
-                mapping.revision_id_foreign_to_bzr((repository.uuid, 1, "bla/bloe")), 
++            repository.get_mapping().revision_id_foreign_to_bzr((repository.uuid, "", 5))))
 +
 +    def test_get_parent_map(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.build_tree({'dc/foo': "data2"})
 +        self.client_commit("dc", "Second Message")
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        revid = repository.generate_revision_id(0, "", mapping)
 +        self.assertEqual({revid: (NULL_REVISION,)}, repository.get_parent_map([revid]))
 +        revid = repository.generate_revision_id(1, "", mapping)
 +        self.assertEqual({revid: (repository.generate_revision_id(0, "", mapping),)}, repository.get_parent_map([revid]))
 +        revid = repository.generate_revision_id(2, "", mapping)
 +        self.assertEqual({revid: (repository.generate_revision_id(1, "", mapping),)},
 +            repository.get_parent_map([revid]))
 +        self.assertEqual({}, repository.get_parent_map(["notexisting"]))
 +
 +    def test_get_revision_delta(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.open_file("foo").modify("data2")
 +        dc.close()
 +
 +        r = Repository.open(repos_url)
 +        r.set_layout(RootLayout())
 +        d1 = r.get_revision_delta(r.get_revision(r.generate_revision_id(1, "", r.get_mapping())))
 +        self.assertEquals(None, d1.unchanged)
 +        self.assertEquals(1, len(d1.added))
 +        self.assertEquals("foo", d1.added[0][0])
 +        self.assertEquals(0, len(d1.modified))
 +        self.assertEquals(0, len(d1.removed))
 +
 +        d2 = r.get_revision_delta(r.get_revision(r.generate_revision_id(2, "", r.get_mapping())))
 +        self.assertEquals(None, d2.unchanged)
 +        self.assertEquals(0, len(d2.added))
 +        self.assertEquals("foo", d2.modified[0][0])
 +        self.assertEquals(0, len(d2.removed))
 +
 +    def test_revision_svk_parent(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/trunk/foo': "data", 'dc/branches/foo': None})
 +        self.client_add("dc/trunk")
 +        self.client_add("dc/branches")
 +        self.client_commit("dc", "My Message")
 +        self.client_update("dc")
 +        self.build_tree({'dc/trunk/foo': "data2"})
 +        repository = Repository.open(repos_url)
 +        repository.set_layout(TrunkLayout(0))
 +        self.client_set_prop("dc/trunk", "svk:merge", 
 +            "%s:/branches/foo:1\n" % repository.uuid)
 +        self.client_commit("dc", "Second Message")
 +        mapping = repository.get_mapping()
 +        self.assertEqual((repository.generate_revision_id(1, "trunk", mapping),
 +            repository.generate_revision_id(1, "branches/foo", mapping)), 
 +                repository.get_revision(
 +                    repository.generate_revision_id(2, "trunk", mapping)).parent_ids)
 +    
 +    def test_get_revision(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repository = Repository.open(repos_url)
 +        self.assertRaises(NoSuchRevision, repository.get_revision, 
 +                "nonexisting")
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.client_update("dc")
 +        self.build_tree({'dc/foo': "data2"})
 +        (num, date, author) = self.client_commit("dc", "Second Message")
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        rev = repository.get_revision(
 +            repository.generate_revision_id(2, "", mapping))
 +        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
 +                rev.parent_ids)
 +        self.assertEqual(rev.revision_id, 
 +                repository.generate_revision_id(2, "", mapping))
 +        self.assertEqual(author, rev.committer)
 +        self.assertIsInstance(rev.properties, dict)
 +
 +    def test_get_revision_zero(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        rev = repository.get_revision(
 +            repository.generate_revision_id(0, "", mapping))
 +        self.assertEqual(repository.generate_revision_id(0, "", mapping), 
 +                         rev.revision_id)
 +        self.assertEqual("", rev.committer)
 +        self.assertEqual({}, rev.properties)
 +        self.assertEqual(0, rev.timezone)
 +
 +    def test_get_ancestry(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repository = Repository.open(repos_url)
 +        self.assertRaises(NoSuchRevision, repository.get_revision, "nonexisting")
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.client_update("dc")
 +        self.build_tree({'dc/foo': "data2"})
 +        self.client_commit("dc", "Second Message")
 +        self.client_update("dc")
 +        self.build_tree({'dc/foo': "data3"})
 +        self.client_commit("dc", "Third Message")
 +        self.client_update("dc")
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual([None, 
 +            repository.generate_revision_id(0, "", mapping),
 +            repository.generate_revision_id(1, "", mapping),
 +            repository.generate_revision_id(2, "", mapping),
 +            repository.generate_revision_id(3, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(3, "", mapping)))
 +        self.assertEqual([None, 
 +            repository.generate_revision_id(0, "", mapping),
 +            repository.generate_revision_id(1, "", mapping),
 +            repository.generate_revision_id(2, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(2, "", mapping)))
 +        self.assertEqual([None,
 +                    repository.generate_revision_id(0, "", mapping),
 +                    repository.generate_revision_id(1, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(1, "", mapping)))
 +        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(0, "", mapping)))
 +        self.assertEqual([None], repository.get_ancestry(NULL_REVISION))
 +
 +    def test_get_ancestry2(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.build_tree({'dc/foo': "data2"})
 +        self.client_commit("dc", "Second Message")
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(0, "", mapping)))
 +        self.assertEqual([None, repository.generate_revision_id(0, "", mapping),
 +            repository.generate_revision_id(1, "", mapping)],
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(1, "", mapping)))
 +        self.assertEqual([None, 
 +            repository.generate_revision_id(0, "", mapping),
 +            repository.generate_revision_id(1, "", mapping),
 +            repository.generate_revision_id(2, "", mapping)], 
 +                repository.get_ancestry(
 +                    repository.generate_revision_id(2, "", mapping)))
 +
 +    def test_get_inventory(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repository = Repository.open(repos_url)
 +        self.assertRaises(NoSuchRevision, repository.get_inventory, 
 +                "nonexisting")
 +        self.build_tree({'dc/foo': "data", 'dc/blah': "other data"})
 +        self.client_add("dc/foo")
 +        self.client_add("dc/blah")
 +        self.client_commit("dc", "My Message") #1
 +        self.client_update("dc")
 +        self.build_tree({'dc/foo': "data2", "dc/bar/foo": "data3"})
 +        self.client_add("dc/bar")
 +        self.client_commit("dc", "Second Message") #2
 +        self.client_update("dc")
 +        self.build_tree({'dc/foo': "data3"})
 +        self.client_commit("dc", "Third Message") #3
 +        self.client_update("dc")
 +        repository = Repository.open(repos_url)
 +        repository.set_layout(RootLayout())
 +        mapping = repository.get_mapping()
 +        inv = repository.get_inventory(
 +                repository.generate_revision_id(1, "", mapping))
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertIsInstance(inv.path2id("foo"), basestring)
 +        inv = repository.get_inventory(
 +            repository.generate_revision_id(2, "", mapping))
 +        self.assertEqual(repository.generate_revision_id(2, "", mapping), 
 +                         inv[inv.path2id("foo")].revision)
 +        self.assertEqual(repository.generate_revision_id(1, "", mapping), 
 +                         inv[inv.path2id("blah")].revision)
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertIsInstance(inv.path2id("foo"), basestring)
 +        self.assertIsInstance(inv.path2id("bar"), basestring)
 +        self.assertIsInstance(inv.path2id("bar/foo"), basestring)
 +
 +    def test_generate_revision_id(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/bla/bloe': None})
 +        self.client_add("dc/bla")
 +        self.client_commit("dc", "bla")
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertEqual(
-         self.assertEqual(mapping.revision_id_foreign_to_bzr((repository.uuid, 0, "")), 
++               mapping.revision_id_foreign_to_bzr((repository.uuid, "bla/bloe", 1)), 
 +            repository.generate_revision_id(1, "bla/bloe", mapping))
 +
 +    def test_generate_revision_id_zero(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
-                 mapping.revision_id_foreign_to_bzr(("invaliduuid", 0, "")))
++        self.assertEqual(mapping.revision_id_foreign_to_bzr((repository.uuid, "", 0)), 
 +                repository.generate_revision_id(0, "", mapping))
 +
 +    def test_lookup_revision_id(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/bloe': None})
 +        self.client_add("dc/bloe")
 +        self.client_commit("dc", "foobar")
 +        repository = Repository.open(repos_url)
 +        self.assertRaises(NoSuchRevision, repository.lookup_revision_id, 
 +            "nonexisting")
 +        mapping = repository.get_mapping()
 +        self.assertEqual(("bloe", 1), 
 +            repository.lookup_revision_id(
 +                repository.generate_revision_id(1, "bloe", mapping))[:2])
 +
 +    def test_lookup_revision_id_invalid_uuid(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        self.assertRaises(NoSuchRevision, 
 +            repository.lookup_revision_id, 
++                mapping.revision_id_foreign_to_bzr(("invaliduuid", "", 0)))
 +        
 +    def test_check(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/foo': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        repository = Repository.open(repos_url)
 +        mapping = repository.get_mapping()
 +        repository.check([
 +            repository.generate_revision_id(0, "", mapping), 
 +            repository.generate_revision_id(1, "", mapping)])
 +
 +    def test_copy_contents_into(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/foo/bla': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.build_tree({'dc/foo/blo': "data2", "dc/bar/foo": "data3", 'dc/foo/bla': "data"})
 +        self.client_add("dc/foo/blo")
 +        self.client_add("dc/bar")
 +        self.client_commit("dc", "Second Message")
 +        repository = Repository.open(repos_url)
 +        repository.set_layout(RootLayout())
 +        mapping = repository.get_mapping()
 +
 +        to_bzrdir = BzrDir.create("e", format.get_rich_root_format())
 +        to_repos = to_bzrdir.create_repository()
 +
 +        repository.copy_content_into(to_repos, 
 +                repository.generate_revision_id(2, "", mapping))
 +
 +        self.assertTrue(repository.has_revision(
 +            repository.generate_revision_id(2, "", mapping)))
 +        self.assertTrue(repository.has_revision(
 +            repository.generate_revision_id(1, "", mapping)))
 +
 +    def test_fetch_property_change_only_trunk(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/trunk/bla': "data"})
 +        self.client_add("dc/trunk")
 +        self.client_commit("dc", "My Message")
 +        self.client_set_prop("dc/trunk", "some:property", "some data\n")
 +        self.client_commit("dc", "My 3")
 +        self.client_set_prop("dc/trunk", "some2:property", "some data\n")
 +        self.client_commit("dc", "My 2")
 +        self.client_set_prop("dc/trunk", "some:property", "some other data\n")
 +        self.client_commit("dc", "My 4")
 +        oldrepos = Repository.open(repos_url)
 +        oldrepos.set_layout(TrunkLayout(0))
 +        self.assertEquals([('trunk', {'trunk': (u'M', None, -1)}, 3), 
 +                           ('trunk', {'trunk': (u'M', None, -1)}, 2), 
 +                           ('trunk', {'trunk/bla': (u'A', None, -1), 'trunk': (u'A', None, -1)}, 1)], 
 +                   [(l.branch_path, l.get_paths(), l.revnum) for l in oldrepos._revmeta_provider.iter_reverse_branch_changes("trunk", 3, 0)])
 +
 +    def test_control_code_msg(self):
 +        if ra.version()[1] >= 5:
 +            raise TestSkipped("Test not runnable with Subversion >= 1.5")
 +        repos_url = self.make_client('d', 'dc')
 +
 +        self.build_tree({'dc/trunk': None})
 +        self.client_add("dc/trunk")
 +        self.client_commit("dc", "\x24")
 +
 +        self.build_tree({'dc/trunk/hosts': 'hej2'})
 +        self.client_add("dc/trunk/hosts")
 +        self.client_commit("dc", "bla\xfcbla") #2
 +
 +        self.build_tree({'dc/trunk/hosts': 'hej3'})
 +        self.client_commit("dc", "a\x0cb") #3
 +
 +        self.build_tree({'dc/branches/foobranch/file': 'foohosts'})
 +        self.client_add("dc/branches")
 +        self.client_commit("dc", "foohosts") #4
 +
 +        oldrepos = Repository.open(repos_url)
 +        oldrepos.set_layout(TrunkLayout(0))
 +        dir = BzrDir.create("f",format=format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +
 +        mapping = oldrepos.get_mapping()
 +
 +        self.assertTrue(newrepos.has_revision(
 +            oldrepos.generate_revision_id(1, "trunk", mapping)))
 +        self.assertTrue(newrepos.has_revision(
 +            oldrepos.generate_revision_id(2, "trunk", mapping)))
 +        self.assertTrue(newrepos.has_revision(
 +            oldrepos.generate_revision_id(3, "trunk", mapping)))
 +        self.assertTrue(newrepos.has_revision(
 +            oldrepos.generate_revision_id(4, "branches/foobranch", mapping)))
 +        self.assertFalse(newrepos.has_revision(
 +            oldrepos.generate_revision_id(4, "trunk", mapping)))
 +        self.assertFalse(newrepos.has_revision(
 +            oldrepos.generate_revision_id(2, "", mapping)))
 +
 +        rev = newrepos.get_revision(oldrepos.generate_revision_id(1, "trunk", mapping))
 +        self.assertEqual("$", rev.message)
 +
 +        rev = newrepos.get_revision(
 +            oldrepos.generate_revision_id(2, "trunk", mapping))
 +        self.assertEqual('bla\xc3\xbcbla', rev.message.encode("utf-8"))
 +
 +        rev = newrepos.get_revision(oldrepos.generate_revision_id(3, "trunk", mapping))
 +        self.assertEqual(u"a\\x0cb", rev.message)
 +
 +    def test_set_layout(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +
 +    def testlhs_revision_parent_none(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +        self.assertEquals(NULL_REVISION, repos._revmeta_provider.get_revision("", 0).get_lhs_parent(repos.get_mapping()))
 +
 +    def testlhs_revision_parent_first(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(RootLayout())
 +        self.build_tree({'dc/adir/afile': "data"})
 +        self.client_add("dc/adir")
 +        self.client_commit("dc", "Initial commit")
 +        mapping = repos.get_mapping()
 +        self.assertEquals(repos.generate_revision_id(0, "", mapping), \
 +                repos._revmeta_provider.get_revision("", 1).get_lhs_parent(mapping))
 +
 +    def testlhs_revision_parent_simple(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/trunk/adir/afile': "data", 
 +                         'dc/trunk/adir/stationary': None,
 +                         'dc/branches/abranch': None})
 +        self.client_add("dc/trunk")
 +        self.client_add("dc/branches")
 +        self.client_commit("dc", "Initial commit")
 +        self.build_tree({'dc/trunk/adir/afile': "bla"})
 +        self.client_commit("dc", "Incremental commit")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(0))
 +        mapping = repos.get_mapping()
 +        self.assertEquals(repos.generate_revision_id(1, "trunk", mapping), \
 +                repos._revmeta_provider.get_revision("trunk", 2).get_lhs_parent(mapping))
 +
 +    def testlhs_revision_parent_copied(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/py/trunk/adir/afile': "data", 
 +                         'dc/py/trunk/adir/stationary': None})
 +        self.client_add("dc/py")
 +        self.client_commit("dc", "Initial commit")
 +        self.client_copy("dc/py", "dc/de")
 +        self.client_commit("dc", "Incremental commit")
 +        self.build_tree({'dc/de/trunk/adir/afile': "bla"})
 +        self.client_commit("dc", "Change de")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(1))
 +        mapping = repos.get_mapping()
 +        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
 +                repos._revmeta_provider.get_revision("de/trunk", 3).get_lhs_parent(mapping))
 +
 +    def test_mainline_revision_copied(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/py/trunk/adir/afile': "data", 
 +                         'dc/py/trunk/adir/stationary': None})
 +        self.client_add("dc/py")
 +        self.client_commit("dc", "Initial commit")
 +        self.build_tree({'dc/de':None})
 +        self.client_add("dc/de")
 +        self.client_copy("dc/py/trunk", "dc/de/trunk")
 +        self.client_commit("dc", "Copy trunk")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(1))
 +        mapping = repos.get_mapping()
 +        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
 +                repos._revmeta_provider.get_revision("de/trunk", 2).get_lhs_parent(mapping))
 +
 +    def test_mainline_revision_nested_deleted(self):
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/py/trunk/adir/afile': "data", 
 +                         'dc/py/trunk/adir/stationary': None})
 +        self.client_add("dc/py")
 +        self.client_commit("dc", "Initial commit")
 +        self.client_copy("dc/py", "dc/de")
 +        self.client_commit("dc", "Incremental commit")
 +        self.client_delete("dc/de/trunk/adir")
 +        self.client_commit("dc", "Another incremental commit")
 +        repos = Repository.open(repos_url)
 +        repos.set_layout(TrunkLayout(1))
 +        mapping = repos.get_mapping()
 +        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
 +                repos._revmeta_provider.get_revision("de/trunk", 3).get_lhs_parent(mapping))
 +
 +    def test_item_keys_introduced_by(self):
 +        repos_url = self.make_repository('d')
 +
 +        cb = self.get_commit_editor(repos_url)
 +        cb.add_file("foo").modify()
 +        cb.close()
 +
 +        cb = self.get_commit_editor(repos_url)
 +        cb.open_file("foo").modify()
 +        cb.close()
 +
 +        b = Branch.open(repos_url)
 +        mapping = b.repository.get_mapping()
 +        ch = list(b.repository.item_keys_introduced_by([b.last_revision()]))
 +        revid = b.last_revision()
 +        self.assertEquals([
 +            ('file', mapping.generate_file_id(b.repository.uuid, 1, "", u"foo"), set([revid])),
 +            ('inventory', None, [revid]),
 +            ('signatures', None, set([])),
 +            ('revisions', None, [revid])], ch)
 +
 +    def test_fetch_file_from_non_branch(self):
 +        repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor(repos_url)
 +        old_trunk = dc.add_dir("old-trunk")
 +        lib = old_trunk.add_dir("old-trunk/lib")
 +        lib.add_file("old-trunk/lib/file").modify("data")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        trunk = dc.add_dir("trunk")
 +        lib = trunk.add_dir("trunk/lib")
 +        lib.add_file("trunk/lib/file", "old-trunk/lib/file")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        oldrepos.set_layout(TrunkLayout(0))
 +        dir = BzrDir.create("f", format.get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +
 +        mapping = oldrepos.get_mapping()
 +        branch = Branch.open("%s/trunk" % repos_url)
 +        if mapping.is_branch("old-trunk"):
 +            self.assertEqual([oldrepos.generate_revision_id(1, "old-trunk", mapping), 
 +                              oldrepos.generate_revision_id(2, "trunk", mapping)], 
 +                              branch.revision_history())
 +        else:
 +            self.assertEqual([oldrepos.generate_revision_id(2, "trunk", mapping)], 
 +                         branch.revision_history())
index 5f6710b5c02b0de61dc53ae8d08a842ec076191f,0000000000000000000000000000000000000000..08c1f3c187d4c3d617de9285654d226ce4042c08
mode 100644,000000..100644
--- /dev/null
@@@ -1,890 -1,0 +1,890 @@@
-             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")),
 +# 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.tests import SubversionTestCase
 +
 +class WorkingSubversionBranch(SubversionTestCase):
 +    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_pull_internal(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)
 +        branches = dc.add_dir("branches")
 +        branches.add_dir("branches/foo", "trunk", 1)
 +        dc.close()
 +
 +        otherbranch = Branch.open(urlutils.join(repos_url, "branches", "foo"))
 +        branch = Branch.open(urlutils.join(repos_url, "trunk"))
 +        result = branch.pull(otherbranch)
 +        self.assertEquals(branch.last_revision(), otherbranch.last_revision())
 +        self.assertEquals(result.new_revid, otherbranch.last_revision())
 +        self.assertEquals(result.old_revid, branch.revision_history()[0])
 +        self.assertEquals(result.old_revno, 1)
 +        self.assertEquals(result.new_revno, 2)
 +        self.assertEquals(result.master_branch, None)
 +        self.assertEquals(result.source_branch, otherbranch)
 +        self.assertEquals(result.target_branch, branch)
 +
 +    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_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 = oldbranch.repository.get_mapping()
 +        self.assertVersionsPresentEquals(texts, host_fileid, [
-         self.assertEqual(mapping.revision_id_foreign_to_bzr((branch.repository.uuid, 1, "")), branch.generate_revision_id(1))
++            mapping.revision_id_foreign_to_bzr((uuid, "trunk", 1)),
++            mapping.revision_id_foreign_to_bzr((uuid, "trunk", 2)),
++            mapping.revision_id_foreign_to_bzr((uuid, "trunk", 3)),
 +            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')
 +        mapping = branch.repository.get_mapping()
++        self.assertEqual(mapping.revision_id_foreign_to_bzr((branch.repository.uuid, "", 1)), 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 63ed71852ab4d3d4dcae0357d7dee86120929ab6,0000000000000000000000000000000000000000..b14967a21f95d31573d1b87b151e96d75a3bd232
mode 100644,000000..100644
--- /dev/null
@@@ -1,413 -1,0 +1,412 @@@
-         mapping = branch.repository.get_mapping()
-         self.assertEqual([mapping.revision_id_foreign_to_bzr(("6987ef2d-cd6b-461f-9991-6f1abef3bd59", 0, ""))], branch.all_revision_ids())
 +# 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
 +
 +"""Full repository conversion tests."""
 +
 +from bzrlib.branch import Branch
 +from bzrlib.bzrdir import BzrDir, format_registry
 +from bzrlib.errors import NotBranchError, NoSuchFile, IncompatibleRepositories
 +from bzrlib.urlutils import local_path_to_url
 +from bzrlib.repository import Repository
 +from bzrlib.tests import TestCaseInTempDir
 +
 +import os, sys
 +
 +from bzrlib.plugins.svn import repos
 +from bzrlib.plugins.svn.layout import RootLayout, TrunkLayout
 +from bzrlib.plugins.svn.convert import convert_repository, NotDumpFile, load_dumpfile
 +from bzrlib.plugins.svn.format import get_rich_root_format
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +
 +class TestLoadDumpfile(TestCaseInTempDir):
 +    def test_loaddumpfile(self):
 +        dumpfile = os.path.join(self.test_dir, "dumpfile")
 +        open(dumpfile, 'w').write(
 +"""SVN-fs-dump-format-version: 2
 +
 +UUID: 6987ef2d-cd6b-461f-9991-6f1abef3bd59
 +
 +Revision-number: 0
 +Prop-content-length: 56
 +Content-length: 56
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-02T13:14:51.972532Z
 +PROPS-END
 +""")
 +        load_dumpfile(dumpfile, "d")
 +        fs = repos.Repository("d").fs()
 +        self.assertEqual("6987ef2d-cd6b-461f-9991-6f1abef3bd59", 
 +                fs.get_uuid())
 +
 +    def test_loaddumpfile_invalid(self):
 +        dumpfile = os.path.join(self.test_dir, "dumpfile")
 +        open(dumpfile, 'w').write("""FooBar\n""")
 +        self.assertRaises(NotDumpFile, load_dumpfile, dumpfile, "d")
 +
 +
 +class TestConversion(SubversionTestCase):
 +    def setUp(self):
 +        super(TestConversion, self).setUp()
 +        self.repos_url = self.make_repository('d')
 +
 +        dc = self.get_commit_editor()
 +        t = dc.add_dir("trunk")
 +        t.add_file("trunk/file").modify("data")
 +        bs = dc.add_dir("branches")
 +        ab = bs.add_dir("branches/abranch")
 +        ab.add_file("branches/abranch/anotherfile").modify("data2")
 +        dc.close()
 +
 +        dc = self.get_commit_editor()
 +        t = dc.open_dir("trunk")
 +        t.open_file("trunk/file").modify("otherdata")
 +        dc.close()
 +
 +    def get_commit_editor(self):
 +        return super(TestConversion, self).get_commit_editor(self.repos_url)
 +
 +    def test_sets_parent_urls(self):
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                           TrunkLayout(0), 
 +                           all=False, create_shared_repo=True)
 +        self.assertEquals(self.repos_url+"/trunk", 
 +                Branch.open("e/trunk").get_parent())
 +        self.assertEquals(self.repos_url+"/branches/abranch", 
 +                Branch.open("e/branches/abranch").get_parent())
 +
 +    def test_fetch_alive(self):
 +        dc = self.get_commit_editor()
 +        bs = dc.open_dir("branches")
 +        sb = bs.add_dir("branches/somebranch")
 +        sb.add_file("branches/somebranch/somefile").modify('data')
 +        dc.close()
 +
 +        dc = self.get_commit_editor()
 +        bs = dc.open_dir("branches")
 +        bs.delete("branches/somebranch")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        convert_repository(oldrepos, "e", 
 +                           TrunkLayout(0), 
 +                           all=False, create_shared_repo=True)
 +        newrepos = Repository.open("e")
 +        oldrepos.set_layout(TrunkLayout(0))
 +        self.assertFalse(newrepos.has_revision(oldrepos.generate_revision_id(2, "branches/somebranch", oldrepos.get_mapping())))
 +
 +    def test_fetch_filebranch(self):
 +        dc = self.get_commit_editor()
 +        bs = dc.open_dir("branches")
 +        bs.add_file("branches/somebranch").modify('data')
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        convert_repository(oldrepos, "e", TrunkLayout(0))
 +        newrepos = Repository.open("e")
 +        oldrepos.set_layout(TrunkLayout(0))
 +        self.assertFalse(newrepos.has_revision(oldrepos.generate_revision_id(2, "branches/somebranch", oldrepos.get_mapping())))
 +
 +    def test_fetch_dead(self):
 +        dc = self.get_commit_editor()
 +        bs = dc.open_dir("branches")
 +        sb = bs.add_dir("branches/somebranch")
 +        sb.add_file("branches/somebranch/somefile").modify('data')
 +        dc.close()
 +
 +        dc = self.get_commit_editor()
 +        bs = dc.open_dir("branches")
 +        bs.delete("branches/somebranch")
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        convert_repository(oldrepos, "e", TrunkLayout(0), 
 +                           all=True, create_shared_repo=True)
 +        newrepos = Repository.open("e")
 +        self.assertTrue(newrepos.has_revision(
 +            oldrepos.generate_revision_id(3, "branches/somebranch", oldrepos.get_mapping())))
 +
 +    def test_fetch_filter(self):
 +        dc = self.get_commit_editor()
 +        branches = dc.open_dir("branches")
 +        dc.add_dir("branches/somebranch")
 +        dc.add_file("branches/somebranch/somefile").modify('data')
 +        dc.close()
 +
 +        dc = self.get_commit_editor()
 +        branches = dc.open_dir("branches")
 +        ab = branches.add_dir("branches/anotherbranch")
 +        ab.add_file("branches/anotherbranch/somefile").modify('data')
 +        dc.close()
 +
 +        oldrepos = Repository.open(self.repos_url)
 +        convert_repository(oldrepos, "e", TrunkLayout(0), 
 +            create_shared_repo=True,
 +            filter_branch=lambda branch: branch.get_branch_path().endswith("somebranch"))
 +        newrepos = Repository.open("e")
 +        self.assertTrue(os.path.exists("e/branches/somebranch"))
 +        self.assertFalse(os.path.exists("e/branches/anotherbranch"))
 +
 +    def test_shared_import_continue(self):
 +        dir = BzrDir.create("e", format=get_rich_root_format())
 +        dir.create_repository(shared=True)
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True)
 +
 +        self.assertTrue(Repository.open("e").is_shared())
 +
 +    def test_shared_import_continue_remove(self):
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True)
 +
 +        dc = self.get_commit_editor()
 +        dc.delete("trunk")
 +        dc.close()
 +
 +        dc = self.get_commit_editor()
 +        trunk = dc.add_dir("trunk")
 +        trunk.add_file("trunk/file").modify()
 +        dc.close()
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                           TrunkLayout(0), create_shared_repo=True)
 +
 +    def test_shared_import_remove_nokeep(self):
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True)
 +
 +        dc = self.get_commit_editor()
 +        dc.delete("trunk")
 +        dc.close()
 +
 +        self.assertTrue(os.path.exists("e/trunk"))
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                           TrunkLayout(0), create_shared_repo=True)
 +
 +        self.assertFalse(os.path.exists("e/trunk"))
 +
 +    def test_shared_import_continue_with_wt(self):
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), working_trees=True)
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), working_trees=True)
 +
 +    def test_shared_import_rootlayout_empty(self):
 +        dir = BzrDir.create("e", format=get_rich_root_format())
 +        dir.create_repository(shared=True)
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                RootLayout(), create_shared_repo=True)
 +
 +    def test_shared_import_with_wt(self):
 +        dir = BzrDir.create("e", format=get_rich_root_format())
 +        dir.create_repository(shared=True)
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True, 
 +                working_trees=True)
 +
 +        self.assertTrue(os.path.isfile(os.path.join(
 +                        self.test_dir, "e", "trunk", "file")))
 +
 +    def test_shared_import_without_wt(self):
 +        dir = BzrDir.create("e", format=get_rich_root_format())
 +        dir.create_repository(shared=True)
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True, 
 +                working_trees=False)
 +
 +        self.assertFalse(os.path.isfile(os.path.join(
 +                        self.test_dir, "e", "trunk", "file")))
 +
 +    def test_shared_import_old_repos_fails(self):
 +        dir = BzrDir.create("e", format=format_registry.make_bzrdir('knit'))
 +        dir.create_repository(shared=True)
 +
 +        self.assertRaises(IncompatibleRepositories, 
 +            lambda: convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True, 
 +                working_trees=False))
 +
 +    def test_shared_import_continue_branch(self):
 +        oldrepos = Repository.open(self.repos_url)
 +        convert_repository(oldrepos, "e", 
 +                TrunkLayout(0), create_shared_repo=True)
 +
 +        mapping = oldrepos.get_mapping()
 +
 +        dc = self.get_commit_editor()
 +        trunk = dc.open_dir("trunk")
 +        trunk.open_file("trunk/file").modify()
 +        dc.close()
 +
 +        self.assertEqual(
 +                Repository.open(self.repos_url).generate_revision_id(2, "trunk", mapping), 
 +                Branch.open("e/trunk").last_revision())
 +
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True)
 +
 +        self.assertEqual(Repository.open(self.repos_url).generate_revision_id(3, "trunk", mapping), 
 +                        Branch.open("e/trunk").last_revision())
 +
 + 
 +    def test_shared_import(self):
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                TrunkLayout(0), create_shared_repo=True)
 +
 +        self.assertTrue(Repository.open("e").is_shared())
 +    
 +    def test_simple(self):
 +        convert_repository(Repository.open(self.repos_url), os.path.join(self.test_dir, "e"), TrunkLayout(0))
 +        self.assertTrue(os.path.isdir(os.path.join(self.test_dir, "e", "trunk")))
 +        self.assertTrue(os.path.isdir(os.path.join(self.test_dir, "e", "branches", "abranch")))
 +
 +    def test_convert_to_nonexistant(self):
 +        self.assertRaises(NoSuchFile, convert_repository, Repository.open(self.repos_url), os.path.join(self.test_dir, "e", "foo", "bar"), TrunkLayout(0))
 +
 +    def test_notshared_import(self):
 +        convert_repository(Repository.open(self.repos_url), "e", 
 +                           TrunkLayout(0), create_shared_repo=False)
 +
 +        self.assertRaises(NotBranchError, Repository.open, "e")
 +
 +class TestConversionFromDumpfile(SubversionTestCase):
 +    def test_dumpfile_open_empty(self):
 +        dumpfile = os.path.join(self.test_dir, "dumpfile")
 +        open(dumpfile, 'w').write(
 +"""SVN-fs-dump-format-version: 2
 +
 +UUID: 6987ef2d-cd6b-461f-9991-6f1abef3bd59
 +
 +Revision-number: 0
 +Prop-content-length: 56
 +Content-length: 56
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-02T13:14:51.972532Z
 +PROPS-END
 +""")
 +        branch_path = os.path.join(self.test_dir, "f")
 +        repos = self.load_dumpfile(dumpfile, 'g')
 +        convert_repository(repos, branch_path, RootLayout())
 +        branch = Repository.open(branch_path)
-         self.assertEqual(mapping.revision_id_foreign_to_bzr(("6987ef2d-cd6b-461f-9991-6f1abef3bd59", 1, 'trunk')), branch.last_revision())
++        self.assertEqual(len(branch.all_revision_ids()), 1)
 +        Branch.open(branch_path)
 +
 +    def load_dumpfile(self, dumpfile, target_path):
 +        load_dumpfile(dumpfile, target_path)
 +        return Repository.open(target_path)
 +
 +    def test_dumpfile_open_empty_trunk(self):
 +        dumpfile = os.path.join(self.test_dir, "dumpfile")
 +        open(dumpfile, 'w').write(
 +"""SVN-fs-dump-format-version: 2
 +
 +UUID: 6987ef2d-cd6b-461f-9991-6f1abef3bd59
 +
 +Revision-number: 0
 +Prop-content-length: 56
 +Content-length: 56
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-02T13:14:51.972532Z
 +PROPS-END
 +""")
 +        branch_path = os.path.join(self.test_dir, "f")
 +        repos = self.load_dumpfile(dumpfile, 'g')
 +        convert_repository(repos, branch_path, TrunkLayout(0))
 +        repository = Repository.open(branch_path)
 +        self.assertEqual([], repository.all_revision_ids())
 +        self.assertRaises(NotBranchError, Branch.open, branch_path)
 +
 +    def test_open_internal(self):
 +        filename = os.path.join(self.test_dir, "dumpfile")
 +        open(filename, 'w').write(
 +"""SVN-fs-dump-format-version: 2
 +
 +UUID: 6987ef2d-cd6b-461f-9991-6f1abef3bd59
 +
 +Revision-number: 0
 +Prop-content-length: 56
 +Content-length: 56
 +
 +K 8
 +svn:date
 +V 27
 +2006-07-02T13:14:51.972532Z
 +PROPS-END
 +
 +Revision-number: 1
 +Prop-content-length: 109
 +Content-length: 109
 +
 +K 7
 +svn:log
 +V 9
 +Add trunk
 +K 10
 +svn:author
 +V 6
 +jelmer
 +K 8
 +svn:date
 +V 27
 +2006-07-02T13:58:02.528258Z
 +PROPS-END
 +
 +Node-path: trunk
 +Node-kind: dir
 +Node-action: add
 +Prop-content-length: 10
 +Content-length: 10
 +
 +PROPS-END
 +
 +
 +Node-path: trunk/bla
 +Node-kind: file
 +Node-action: add
 +Prop-content-length: 10
 +Text-content-length: 5
 +Text-content-md5: 6137cde4893c59f76f005a8123d8e8e6
 +Content-length: 15
 +
 +PROPS-END
 +data
 +
 +
 +""")
 +        repos = self.load_dumpfile(filename, 'g')
 +        convert_repository(repos, os.path.join(self.test_dir, "e"), 
 +                           TrunkLayout(0))
 +        mapping = repos.get_mapping()
 +        abspath = self.test_dir
 +        if sys.platform == 'win32':
 +            abspath = '/' + abspath
 +        branch = Branch.open(os.path.join(self.test_dir, "e", "trunk"))
 +        self.assertEqual(local_path_to_url(os.path.join(self.test_dir, "e", "trunk")), branch.base.rstrip("/"))
++        self.assertEqual(mapping.revision_id_foreign_to_bzr(("6987ef2d-cd6b-461f-9991-6f1abef3bd59", 'trunk', 1)), branch.last_revision())
 +
index ace589d77bdb9d25839ef5f6418fe71ef5ee48f6,0000000000000000000000000000000000000000..fded5aee26bfa2faa3077826401b11d3591b9aee
mode 100644,000000..100644
--- /dev/null
@@@ -1,208 -1,0 +1,208 @@@
-         self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv4()), 
 +# -*- 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, NotBranchError
 +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, 
 +                     escape_svn_path, unescape_svn_path)
 +from bzrlib.plugins.svn.mapping2 import BzrSvnMappingv1, BzrSvnMappingv2
 +from bzrlib.plugins.svn.mapping3 import BzrSvnMappingv3FileProps
 +from bzrlib.plugins.svn.mapping4 import BzrSvnMappingv4
 +
 +
 +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"))
 +
 +
 +
 +def sha1(text):
 +    return sha.new(text).hexdigest()
 +
 +
 +class ParseRevisionIdTests(object):
 +
 +    def test_v4(self):
-         self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv3FileProps(TrunkBranchingScheme())), 
++        self.assertEqual((("uuid", "trunk", 1), BzrSvnMappingv4()), 
 +                mapping_registry.parse_revision_id("svn-v3:uuid:trunk:1"))
 +
 +    def test_v3(self):
-         self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv3FileProps(TrunkBranchingScheme())), 
++        self.assertEqual((("uuid", "trunk", 1), BzrSvnMappingv3FileProps(TrunkBranchingScheme())), 
 +                mapping_registry.parse_revision_id("svn-v3-trunk0:uuid:trunk:1"))
 +
 +    def test_v3_undefined(self):
-         self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv2()), 
++        self.assertEqual((("uuid", "trunk", 1), BzrSvnMappingv3FileProps(TrunkBranchingScheme())), 
 +                mapping_registry.parse_revision_id("svn-v3-undefined:uuid:trunk:1"))
 +
 +    def test_v2(self):
-         self.assertEqual(("uuid", "trunk", 1, BzrSvnMappingv1()), 
++        self.assertEqual((("uuid", "trunk", 1), BzrSvnMappingv2()), 
 +                         mapping_registry.parse_revision_id("svn-v2:1@uuid-trunk"))
 +
 +    def test_v1(self):
++        self.assertEqual((("uuid", "trunk", 1), BzrSvnMappingv1()), 
 +                         mapping_registry.parse_revision_id("svn-v1:1@uuid-trunk"))
 +
 +    def test_except(self):
 +        self.assertRaises(InvalidRevisionId, 
 +                         mapping_registry.parse_revision_id, "svn-v0:1@uuid-trunk")
 +
 +    def test_except_nonsvn(self):
 +        self.assertRaises(InvalidRevisionId, 
 +                         mapping_registry.parse_revision_id, "blah")
 +
 +
 +class EscapeTest(TestCase):
 +    def test_escape_svn_path_none(self):      
 +        self.assertEqual("", escape_svn_path(""))
 +
 +    def test_escape_svn_path_simple(self):
 +        self.assertEqual("ab", escape_svn_path("ab"))
 +
 +    def test_escape_svn_path_percent(self):
 +        self.assertEqual("a%25b", escape_svn_path("a%b"))
 +
 +    def test_escape_svn_path_whitespace(self):
 +        self.assertEqual("foobar%20", escape_svn_path("foobar "))
 +
 +    def test_escape_svn_path_slash(self):
 +        self.assertEqual("foobar%2F", escape_svn_path("foobar/"))
 +
 +    def test_escape_svn_path_special_char(self):
 +        self.assertEqual("foobar%8A", escape_svn_path("foobar\x8a"))
 +
 +    def test_unescape_svn_path_slash(self):
 +        self.assertEqual("foobar/", unescape_svn_path("foobar%2F"))
 +
 +    def test_unescape_svn_path_none(self):
 +        self.assertEqual("foobar", unescape_svn_path("foobar"))
 +
 +    def test_unescape_svn_path_percent(self):
 +        self.assertEqual("foobar%b", unescape_svn_path("foobar%25b"))
 +
 +    def test_escape_svn_path_nordic(self):
 +        self.assertEqual("foobar%C3%A6", escape_svn_path(u"foobar\xe6".encode("utf-8")))
index 0711a9496a8637d26d076654a12701065155d0a4,0000000000000000000000000000000000000000..3ae1a40831255030b47a6c1740ec0876b7b40fed
mode 100644,000000..100644
--- /dev/null
@@@ -1,291 -1,0 +1,291 @@@
-                 mapping.revision_id_foreign_to_bzr((repos.uuid, 1, "")), 
 +# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
 +
 +# This program is free software; you can redistribute it and/or modify
 +# it under the terms of the GNU General Public License as published by
 +# the Free Software Foundation; either version 3 of the License, or
 +# (at your option) any later version.
 +
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU General Public License for more details.
 +
 +# You should have received a copy of the GNU General Public License
 +# along with this program; if not, write to the Free Software
 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +"""Basis and revision tree tests."""
 +
 +import os
 +
 +from bzrlib.inventory import Inventory, TreeReference
 +from bzrlib.osutils import has_symlinks
 +from bzrlib.repository import Repository
 +from bzrlib.revision import NULL_REVISION
 +from bzrlib.tests import TestSkipped
 +from bzrlib.workingtree import WorkingTree
 +
 +from bzrlib.plugins.svn import errors
 +from bzrlib.plugins.svn.core import SubversionException
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +from bzrlib.plugins.svn.tree import SvnBasisTree, inventory_add_external
 +
 +
 +class TestBasisTree(SubversionTestCase):
 +    def test_executable(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        f = dc.add_file("file")
 +        f.modify("x")
 +        f.change_prop("svn:executable", "*")
 +        dc.close()
 +
 +        self.client_update("dc")
 +
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertTrue(tree.inventory[tree.inventory.path2id("file")].executable)
 +
 +    def test_executable_changed(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("file").modify("x")
 +        dc.close()
 +
 +        self.client_update("dc")
 +        self.client_set_prop("dc/file", "svn:executable", "*")
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertFalse(tree.inventory[tree.inventory.path2id("file")].executable)
 +
 +    def test_symlink(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        file = dc.add_file("file")
 +        file.modify("link target")
 +        file.change_prop("svn:special", "*")
 +        dc.close()
 +
 +        self.client_update("dc")
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertEqual('symlink', 
 +                         tree.inventory[tree.inventory.path2id("file")].kind)
 +        self.assertEqual("target",
 +                         tree.inventory[tree.inventory.path2id("file")].symlink_target)
 +
 +    def test_symlink_with_newlines_in_target(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        file = dc.add_file("file")
 +        file.modify("link target\nbar\nbla")
 +        file.change_prop("svn:special", "*")
 +        dc.close()
 +
 +        self.client_update("dc")
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertEqual('symlink', 
 +                         tree.inventory[tree.inventory.path2id("file")].kind)
 +        self.assertEqual("target\nbar\nbla",
 +                         tree.inventory[tree.inventory.path2id("file")].symlink_target)
 +
 +    def test_symlink_not_special(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        file = dc.add_file("file")
 +        file.modify("fsdfdslhfdsk h")
 +        file.change_prop("svn:special", "*")
 +        file2 = dc.add_file("file2")
 +        file.modify("a")
 +        file.change_prop("svn:special", "*")
 +        dc.close()
 +
 +        try:
 +            self.client_update("dc")
 +        except SubversionException, (msg, num):
 +            if num == errors.ERR_WC_BAD_ADM_LOG:
 +                raise TestSkipped("Unable to run test with svn 1.4")
 +            raise
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertEqual('file', 
 +                         tree.inventory[tree.inventory.path2id("file")].kind)
 +
 +    def test_symlink_next(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("bla").modify("p")
 +        file = dc.add_file("file")
 +        file.modify("link target")
 +        file.change_prop("svn:special", "*")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("bla").modify("pa")
 +        dc.close()
 +
 +        self.client_update("dc")
 +
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertEqual('symlink', 
 +                         tree.inventory[tree.inventory.path2id("file")].kind)
 +        self.assertEqual("target",
 +                         tree.inventory[tree.inventory.path2id("file")].symlink_target)
 +
 +    def test_annotate_iter(self):
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("file").modify("x\n")
 +        dc.close()
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.open_file("file").modify("x\ny\n")
 +        dc.close()
 +
 +        self.client_update('dc')
 +        tree = SvnBasisTree(WorkingTree.open("dc"))
 +        self.assertRaises(NotImplementedError, tree.annotate_iter, tree.path2id("file"))
 +
 +    def test_executable_link(self):
 +        if not has_symlinks():
 +            return
 +        repos_url = self.make_client("d", "dc")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        file = dc.add_file("file")
 +        file.modify("link target")
 +        file.change_prop("svn:special", "*")
 +        file.change_prop("svn:executable", "*")
 +        dc.close()
 +
 +        try:
 +            self.client_update("dc")
 +        except SubversionException, (msg, num):
 +            if num == errors.ERR_WC_BAD_ADM_LOG:
 +                raise TestSkipped("Unable to run test with svn 1.4")
 +            raise
 +
 +        wt = WorkingTree.open("dc")
 +        tree = SvnBasisTree(wt)
 +        self.assertFalse(tree.inventory[tree.inventory.path2id("file")].executable)
 +        self.assertFalse(wt.inventory[wt.inventory.path2id("file")].executable)
 +
 +
 +class TestInventoryExternals(SubversionTestCase):
 +    def test_add_nested_norev(self):
 +        """Add a nested tree with no specific revision referenced."""
 +        repos_url = self.make_client('d', 'dc')
 +        repos = Repository.open(repos_url)
 +        mapping = repos.get_mapping()
 +        inv = Inventory(root_id='blabloe')
 +        inventory_add_external(inv, 'blabloe', 'blie/bla', 
-              revision=mapping.revision_id_foreign_to_bzr((repos.uuid, 1, ""))), 
++                mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1)), 
 +                None, repos_url)
 +        self.assertEqual(TreeReference(
 +            mapping.generate_file_id(repos.uuid, 0, "", u""),
 +             'bla', inv.path2id('blie'), 
-             mapping.revision_id_foreign_to_bzr((repos.uuid, 1, "")), None, 
++             revision=mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1))), 
 +             inv[inv.path2id('blie/bla')])
 +
 +    def test_add_simple_norev(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repos = Repository.open(repos_url)
 +        mapping = repos.get_mapping()
 +        inv = Inventory(root_id='blabloe')
 +        inventory_add_external(inv, 'blabloe', 'bla', 
-              revision=mapping.revision_id_foreign_to_bzr((repos.uuid, 1, ""))), 
++            mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1)), None, 
 +            repos_url)
 +
 +        self.assertEqual(TreeReference(
 +            mapping.generate_file_id(repos.uuid, 0, "", u""),
 +             'bla', 'blabloe', 
-             mapping.revision_id_foreign_to_bzr((repos.uuid, 1, "")), 0, repos_url)
++             revision=mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1))), 
 +             inv[inv.path2id('bla')])
 +
 +    def test_add_simple_rev(self):
 +        repos_url = self.make_client('d', 'dc')
 +        repos = Repository.open(repos_url)
 +        inv = Inventory(root_id='blabloe')
 +        mapping = repos.get_mapping()
 +        inventory_add_external(inv, 'blabloe', 'bla', 
-             revision=mapping.revision_id_foreign_to_bzr((repos.uuid, 1, "")),
++            mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1)), 0, repos_url)
 +        expected_ie = TreeReference(mapping.generate_file_id(repos.uuid, 0, "", u""),
 +            'bla', 'blabloe', 
-         self.assertEqual(mapping.revision_id_foreign_to_bzr((repos.uuid, 1, "")), 
++            revision=mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1)),
 +            reference_revision=NULL_REVISION)
 +        ie = inv[inv.path2id('bla')]
 +        self.assertEqual(NULL_REVISION, ie.reference_revision)
++        self.assertEqual(mapping.revision_id_foreign_to_bzr((repos.uuid, "", 1)), 
 +                         ie.revision)
 +        self.assertEqual(expected_ie, inv[inv.path2id('bla')])
 +
 +
 +class TestSvnRevisionTree(SubversionTestCase):
 +    def setUp(self):
 +        super(TestSvnRevisionTree, self).setUp()
 +        repos_url = self.make_client('d', 'dc')
 +        self.build_tree({'dc/foo/bla': "data"})
 +        self.client_add("dc/foo")
 +        self.client_commit("dc", "My Message")
 +        self.repos = Repository.open(repos_url)
 +        mapping = self.repos.get_mapping()
 +        self.inventory = self.repos.get_inventory(
 +                self.repos.generate_revision_id(1, "", mapping))
 +        self.tree = self.repos.revision_tree(
 +                self.repos.generate_revision_id(1, "", mapping))
 +
 +    def test_inventory(self):
 +        self.assertIsInstance(self.tree.inventory, Inventory)
 +        self.assertEqual(self.inventory, self.tree.inventory)
 +
 +    def test_get_parent_ids(self):
 +        mapping = self.repos.get_mapping()
 +        self.assertEqual((self.repos.generate_revision_id(0, "", mapping),), self.tree.get_parent_ids())
 +
 +    def test_get_parent_ids_zero(self):
 +        mapping = self.repos.get_mapping()
 +        tree = self.repos.revision_tree(
 +                self.repos.generate_revision_id(0, "", mapping))
 +        self.assertEqual((), tree.get_parent_ids())
 +
 +    def test_get_revision_id(self):
 +        mapping = self.repos.get_mapping()
 +        self.assertEqual(self.repos.generate_revision_id(1, "", mapping),
 +                         self.tree.get_revision_id())
 +
 +    def test_get_file_lines(self):
 +        self.assertEqual(["data"], 
 +                self.tree.get_file_lines(self.inventory.path2id("foo/bla")))
 +
 +    def test_executable(self):
 +        self.client_set_prop("dc/foo/bla", "svn:executable", "*")
 +        self.client_commit("dc", "My Message")
 +
 +        mapping = self.repos.get_mapping()
 +        
 +        inventory = self.repos.get_inventory(
 +                self.repos.generate_revision_id(2, "", mapping))
 +
 +        self.assertTrue(inventory[inventory.path2id("foo/bla")].executable)
 +
 +    def test_symlink(self):
 +        if not has_symlinks():
 +            return
 +        os.symlink('foo/bla', 'dc/bar')
 +        self.client_add('dc/bar')
 +        self.client_commit("dc", "My Message")
 +
 +        mapping = self.repos.get_mapping()
 +        
 +        inventory = self.repos.get_inventory(
 +                self.repos.generate_revision_id(2, "", mapping))
 +
 +        self.assertEqual('symlink', inventory[inventory.path2id("bar")].kind)
 +        self.assertEqual('foo/bla', 
 +                inventory[inventory.path2id("bar")].symlink_target)
 +
 +    def test_not_executable(self):
 +        self.assertFalse(self.inventory[
 +            self.inventory.path2id("foo/bla")].executable)
index 8f70c200263e9b47de2e2046cb2f0530d1dc8c19,0000000000000000000000000000000000000000..ba9a4ff36964498fe9742cae6ddb93f749ad3d13
mode 100644,000000..100644
--- /dev/null
@@@ -1,343 -1,0 +1,343 @@@
-         self.assertEquals({}, generate_upgrade_map(BzrSvnMappingv3FileProps(TrunkBranchingScheme()), ["bla", "bloe"]))
 +# Copyright (C) 2007 Jelmer Vernooij <jelmer@samba.org>
 +
 +# This program is free software; you can redistribute it and/or modify
 +# it under the terms of the GNU General Public License as published by
 +# the Free Software Foundation; either version 3 of the License, or
 +# (at your option) any later version.
 +
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU General Public License for more details.
 +
 +# You should have received a copy of the GNU General Public License
 +# along with this program; if not, write to the Free Software
 +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 +
 +"""Mapping upgrade tests."""
 +
 +from bzrlib.bzrdir import BzrDir
 +from bzrlib.repository import Repository
 +from bzrlib.tests import TestCase, TestSkipped
 +
 +from bzrlib.plugins.svn.format import get_rich_root_format
 +from bzrlib.plugins.svn.mapping import mapping_registry
 +from bzrlib.plugins.svn.mapping3 import BzrSvnMappingv3FileProps
 +from bzrlib.plugins.svn.mapping3.scheme import TrunkBranchingScheme
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +from bzrlib.plugins.svn.foreign.upgrade import (upgrade_repository, upgrade_branch,
 +                     upgrade_workingtree, UpgradeChangesContent, RebaseNotPresent,
 +                     create_upgraded_revid, generate_upgrade_map)
 +
 +class TestUpgradeChangesContent(TestCase):
 +    def test_init(self):
 +        x = UpgradeChangesContent("revisionx")
 +        self.assertEqual("revisionx", x.revid)
 +
 +
 +class ParserTests(TestCase):
 +    def test_create_upgraded_revid_new(self):
 +        self.assertEqual("bla-svn3-upgrade",
 +                         create_upgraded_revid("bla", "-svn3"))
 +
 +    def test_create_upgraded_revid_upgrade(self):
 +        self.assertEqual("bla-svn3-upgrade",
 +                         create_upgraded_revid("bla-svn1-upgrade", "-svn3"))
 +
 +
 +def skip_no_rebase(unbound):
 +    def check_error(self, *args, **kwargs):
 +        try:
 +            return unbound(self, *args, **kwargs)
 +        except RebaseNotPresent, e:
 +            raise TestSkipped(e)
 +    check_error.__doc__ = unbound.__doc__
 +    check_error.__name__ = unbound.__name__
 +    return check_error
 +
 +
 +class UpgradeTests(SubversionTestCase):
 +    @skip_no_rebase
 +    def test_no_custom(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a","w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +
 +        self.assertTrue(newrepos.has_revision("svn-v1:1@%s-" % oldrepos.uuid))
 +
 +        mapping = oldrepos.get_mapping()
 +        upgrade_repository(newrepos, oldrepos, new_mapping=mapping, mapping_registry=mapping_registry, allow_changes=True)
 +
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +
 +    @skip_no_rebase
 +    def test_single_custom(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +
 +        mapping = oldrepos.get_mapping()
 +        upgrade_repository(newrepos, oldrepos, new_mapping=mapping, mapping_registry=mapping_registry, allow_changes=True)
 +
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +        self.assertTrue(newrepos.has_revision("customrev%s-upgrade" % mapping.upgrade_suffix))
 +        newrepos.lock_read()
 +        self.assertTrue((oldrepos.generate_revision_id(1, "", mapping),),
 +                        tuple(newrepos.get_revision("customrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        newrepos.unlock()
 +
 +    @skip_no_rebase
 +    def test_single_keep_parent_fileid(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add(["a"], ["someid"])
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        wt.rename_one("a", "b")
 +        file("f/a", 'w').write("moredata")
 +        wt.add(["a"], ["specificid"])
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +
 +        mapping = oldrepos.get_mapping()
 +        upgrade_repository(newrepos, oldrepos, new_mapping=mapping, mapping_registry=mapping_registry, allow_changes=True)
 +        tree = newrepos.revision_tree("customrev%s-upgrade" % mapping.upgrade_suffix)
 +        self.assertEqual("specificid", tree.inventory.path2id("a"))
 +        self.assertEqual(mapping.generate_file_id(oldrepos.uuid, 1, "", u"a"), 
 +                         tree.inventory.path2id("b"))
 +
 +    @skip_no_rebase
 +    def test_single_custom_continue(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.add_file("b").modify("c")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        oldrepos.copy_content_into(newrepos)
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        file("f/b", "w").write("c")
 +        wt.add("a")
 +        wt.add("b")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        file("f/b", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +
 +        tree = newrepos.revision_tree("svn-v1:1@%s-" % oldrepos.uuid)
 +
 +        newrepos.lock_write()
 +        newrepos.start_write_group()
 +
 +        mapping = oldrepos.get_mapping()
 +        fileid = tree.inventory.path2id("a")
 +        revid = "customrev%s-upgrade" % mapping.upgrade_suffix
 +        newrepos.texts.add_lines((fileid, revid), 
 +                [(fileid, "svn-v1:1@%s-" % oldrepos.uuid)],
 +                tree.get_file(fileid).readlines())
 +
 +        newrepos.commit_write_group()
 +        newrepos.unlock()
 +
 +        upgrade_repository(newrepos, oldrepos, new_mapping=mapping, mapping_registry=mapping_registry, 
 +                           allow_changes=True)
 +
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +        self.assertTrue(newrepos.has_revision("customrev%s-upgrade" % mapping.upgrade_suffix))
 +        newrepos.lock_read()
 +        self.assertTrue((oldrepos.generate_revision_id(1, "", mapping),),
 +                        tuple(newrepos.get_revision("customrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        newrepos.unlock()
 +
 +    @skip_no_rebase
 +    def test_more_custom(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
 +        mapping = oldrepos.get_mapping()
 +        renames = upgrade_repository(newrepos, oldrepos, new_mapping=mapping, mapping_registry=mapping_registry, 
 +                                     allow_changes=True)
 +        self.assertEqual({
 +            'svn-v1:1@%s-' % oldrepos.uuid: 'svn-v3-none:%s::1' % oldrepos.uuid,
 +            "customrev": "customrev%s-upgrade" % mapping.upgrade_suffix,
 +            "anotherrev": "anotherrev%s-upgrade" % mapping.upgrade_suffix},
 +            renames)
 +
 +        self.assertTrue(newrepos.has_revision(oldrepos.generate_revision_id(1, "", mapping)))
 +        self.assertTrue(newrepos.has_revision("customrev%s-upgrade" % mapping.upgrade_suffix))
 +        self.assertTrue(newrepos.has_revision("anotherrev%s-upgrade" % mapping.upgrade_suffix))
 +        newrepos.lock_read()
 +        self.assertTrue((oldrepos.generate_revision_id(1, "", mapping),),
 +                        tuple(newrepos.get_revision("customrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        self.assertTrue(("customrev-%s-upgrade" % mapping.upgrade_suffix,),
 +                        tuple(newrepos.get_revision("anotherrev%s-upgrade" % mapping.upgrade_suffix).parent_ids))
 +        newrepos.unlock()
 +
 +    @skip_no_rebase
 +    def test_more_custom_branch(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
 +        upgrade_branch(b, oldrepos, new_mapping=oldrepos.get_mapping(), mapping_registry=mapping_registry, allow_changes=True)
 +        mapping = oldrepos.get_mapping()
 +        self.assertEqual([oldrepos.generate_revision_id(0, "", mapping),
 +                          oldrepos.generate_revision_id(1, "", mapping),
 +                          "customrev%s-upgrade" % mapping.upgrade_suffix,
 +                          "anotherrev%s-upgrade" % mapping.upgrade_suffix
 +                          ], b.revision_history())
 +
 +    @skip_no_rebase
 +    def test_workingtree(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        newrepos = dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
 +        mapping = oldrepos.get_mapping()
 +        upgrade_workingtree(wt, oldrepos, new_mapping=mapping, 
 +                mapping_registry=mapping_registry, allow_changes=True)
 +        self.assertEquals(wt.last_revision(), b.last_revision())
 +        self.assertEqual([oldrepos.generate_revision_id(0, "", mapping),
 +                          oldrepos.generate_revision_id(1, "", mapping),
 +                          "customrev%s-upgrade" % mapping.upgrade_suffix,
 +                          "anotherrev%s-upgrade" % mapping.upgrade_suffix
 +                          ], b.revision_history())
 +
 +    @skip_no_rebase
 +    def test_branch_none(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("a").modify("b")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("b")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="blarev")
 +        file("f/a", 'w').write("moredata")
 +        wt.commit(message='fix moredata', rev_id="customrev")
 +        file("f/a", 'w').write("blackfield")
 +        wt.commit(message='fix it again', rev_id="anotherrev")
 +
 +        upgrade_branch(b, oldrepos, new_mapping=oldrepos.get_mapping(), mapping_registry=mapping_registry)
 +        self.assertEqual(["blarev", "customrev", "anotherrev"],
 +                b.revision_history())
 +
 +    @skip_no_rebase
 +    def test_raise_incompat(self):
 +        repos_url = self.make_repository("a")
 +
 +        dc = self.get_commit_editor(repos_url)
 +        dc.add_file("d").modify("e")
 +        dc.close()
 +
 +        oldrepos = Repository.open(repos_url)
 +        dir = BzrDir.create("f", format=get_rich_root_format())
 +        dir.create_repository()
 +        b = dir.create_branch()
 +        wt = dir.create_workingtree()
 +        file("f/a", "w").write("c")
 +        wt.add("a")
 +        wt.commit(message="data", rev_id="svn-v1:1@%s-" % oldrepos.uuid)
 +
 +        self.assertRaises(UpgradeChangesContent, lambda: upgrade_branch(b, oldrepos, new_mapping=oldrepos.get_mapping(), mapping_registry=mapping_registry))
 +
 +
 +class TestGenerateUpdateMapTests(TestCase):
 +    def test_nothing(self):
-         self.assertEquals({"svn-v2:12@65390229-12b7-0310-b90b-f21a5aa7ec8e-trunk": "svn-v3-trunk0:65390229-12b7-0310-b90b-f21a5aa7ec8e:trunk:12"}, generate_upgrade_map(BzrSvnMappingv3FileProps(TrunkBranchingScheme()), ["svn-v2:12@65390229-12b7-0310-b90b-f21a5aa7ec8e-trunk", "bloe", "blaaa"]))
++        self.assertEquals({}, generate_upgrade_map(BzrSvnMappingv3FileProps(TrunkBranchingScheme()), ["bla", "bloe"], mapping_registry))
 +
 +    def test_v2_to_v3(self):
++        self.assertEquals({"svn-v2:12@65390229-12b7-0310-b90b-f21a5aa7ec8e-trunk": "svn-v3-trunk0:65390229-12b7-0310-b90b-f21a5aa7ec8e:trunk:12"}, generate_upgrade_map(BzrSvnMappingv3FileProps(TrunkBranchingScheme()), ["svn-v2:12@65390229-12b7-0310-b90b-f21a5aa7ec8e-trunk", "bloe", "blaaa"], mapping_registry))
index 7f1a7528eddb7600150b28a086eb2d4db52f5760,0000000000000000000000000000000000000000..348fbaac031bccf96041f2af0c6afd6ee7f13392
mode 100644,000000..100644
--- /dev/null
@@@ -1,685 -1,0 +1,685 @@@
-             tree.branch.mapping.revision_id_foreign_to_bzr(("a-uuid-foo", 1, "branch/path")), "c"])
 +# Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
 +# -*- coding: utf-8 -*-
 +
 +# 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
 +
 +"""Working tree tests."""
 +
 +from bzrlib.branch import Branch
 +from bzrlib.bzrdir import BzrDir
 +from bzrlib.errors import NoSuchFile, OutOfDateTree
 +from bzrlib.inventory import Inventory
 +from bzrlib.osutils import has_symlinks, supports_executable
 +from bzrlib.tests import TestCase
 +from bzrlib.trace import mutter
 +from bzrlib.workingtree import WorkingTree
 +
 +from bzrlib.plugins.svn.transport import svn_config
 +from bzrlib.plugins.svn.tests import SubversionTestCase
 +from bzrlib.plugins.svn.workingtree import generate_ignore_list
 +
 +import os
 +
 +class TestWorkingTree(SubversionTestCase):
 +    def test_add_duplicate(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        self.client_add("dc/bl")
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl"])
 +
 +    def test_add_unexisting(self):
 +        self.make_client('a', 'dc')
 +        tree = WorkingTree.open("dc")
 +        self.assertRaises(NoSuchFile, tree.add, ["bl"])
 +
 +    def test_add(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_special_char(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({u"dc/I²C": "data"})
 +        self.client_add("dc/I²C")
 +        tree = WorkingTree.open("dc")
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename(u"I²C"))
 +
 +    def test_smart_add_file(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.smart_add(["dc/bl"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_smart_add_recurse(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl/foo": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.smart_add(["dc/bl"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertTrue(inv.has_filename("bl/foo"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_smart_add_recurse_more(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl/foo/da": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.smart_add(["dc/bl"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertTrue(inv.has_filename("bl/foo"))
 +        self.assertTrue(inv.has_filename("bl/foo/da"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_smart_add_more(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl/foo/da": "data", "dc/ha": "contents"})
 +        tree = WorkingTree.open("dc")
 +        tree.smart_add(["dc/bl", "dc/ha"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertTrue(inv.has_filename("bl/foo"))
 +        self.assertTrue(inv.has_filename("bl/foo/da"))
 +        self.assertTrue(inv.has_filename("ha"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_smart_add_ignored(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/.bzrignore": "bl/ha"})
 +        self.build_tree({"dc/bl/foo/da": "data", "dc/bl/ha": "contents"})
 +        tree = WorkingTree.open("dc")
 +        tree.smart_add(["dc/bl"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertTrue(inv.has_filename("bl/foo"))
 +        self.assertTrue(inv.has_filename("bl/foo/da"))
 +        self.assertFalse(inv.has_filename("ha"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_smart_add_toplevel(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl/foo/da": "data", "dc/ha": "contents"})
 +        tree = WorkingTree.open("dc")
 +        tree.smart_add(["dc"])
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertTrue(inv.has_filename("bl/foo"))
 +        self.assertTrue(inv.has_filename("bl/foo/da"))
 +        self.assertTrue(inv.has_filename("ha"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_add_nolist(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add("bl")
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertFalse(inv.has_filename("aa"))
 +
 +    def test_add_nolist_withid(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add("bl", "bloe")
 +
 +        inv = tree.read_working_inventory()
 +        self.assertIsInstance(inv, Inventory)
 +        self.assertTrue(inv.has_filename("bl"))
 +        self.assertFalse(inv.has_filename("aa"))
 +        self.assertEqual("bloe", tree.inventory.path2id("bl"))
 +
 +    def test_add_not_recursive(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl/file": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl"])
 +
 +        tree = WorkingTree.open("dc")
 +        self.assertTrue(tree.inventory.has_filename("bl"))
 +        self.assertFalse(tree.inventory.has_filename("bl/file"))
 +
 +    def test_add_nested(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl/file": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl", "bl/file"])
 +
 +        tree = WorkingTree.open("dc")
 +        self.assertTrue(tree.inventory.has_filename("bl"))
 +        self.assertTrue(tree.inventory.has_filename("bl/file"))
 +
 +    def test_lock_write(self):
 +        self.make_client('a', 'dc')
 +        tree = WorkingTree.open("dc")
 +        tree.lock_write()
 +
 +    def test_lock_read(self):
 +        self.make_client('a', 'dc')
 +        tree = WorkingTree.open("dc")
 +        tree.lock_read()
 +
 +    def test_unlock(self):
 +        self.make_client('a', 'dc')
 +        tree = WorkingTree.open("dc")
 +        tree.lock_read()
 +        tree.unlock()
 +
 +    def test_get_ignore_list_empty(self):
 +        self.make_client('a', 'dc')
 +        tree = WorkingTree.open("dc")
 +        self.assertEqual(set([".svn"] + svn_config.get_default_ignores()), tree.get_ignore_list())
 +
 +    def test_get_ignore_list_onelevel(self):
 +        self.make_client('a', 'dc')
 +        self.client_set_prop("dc", "svn:ignore", "*.d\n*.c\n")
 +        tree = WorkingTree.open("dc")
 +        self.assertEqual(set([".svn"] + svn_config.get_default_ignores() + ["./*.d", "./*.c"]), tree.get_ignore_list())
 +
 +    def test_get_ignore_list_morelevel(self):
 +        self.make_client('a', 'dc')
 +        self.client_set_prop("dc", "svn:ignore", "*.d\n*.c\n")
 +        self.build_tree({'dc/x': None})
 +        self.client_add("dc/x")
 +        self.client_set_prop("dc/x", "svn:ignore", "*.e\n")
 +        tree = WorkingTree.open("dc")
 +        self.assertEqual(set([".svn"] + svn_config.get_default_ignores() + ["./*.d", "./*.c", "./x/*.e"]), tree.get_ignore_list())
 +
 +    def test_add_reopen(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl"])
 +
 +        inv = WorkingTree.open("dc").read_working_inventory()
 +        self.assertTrue(inv.has_filename("bl"))
 +
 +    def test_remove(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl"])
 +        tree.remove(["bl"])
 +        inv = tree.read_working_inventory()
 +        self.assertFalse(inv.has_filename("bl"))
 +
 +    def test_remove_dup(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        tree = WorkingTree.open("dc")
 +        tree.add(["bl"])
 +        os.remove("dc/bl")
 +        inv = tree.read_working_inventory()
 +        self.assertFalse(inv.has_filename("bl"))
 +
 +    def test_is_control_file(self):
 +        self.make_client('a', 'dc')
 +        tree = WorkingTree.open("dc")
 +        self.assertTrue(tree.is_control_filename(".svn"))
 +        self.assertFalse(tree.is_control_filename(".bzr"))
 +
 +    def test_revert(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        self.client_add("dc/bl")
 +        self.client_commit("dc", "Bla")
 +        self.client_update("dc")
 +        tree = WorkingTree.open("dc")
 +        os.remove("dc/bl")
 +        tree.revert(["bl"])
 +        self.assertFalse(tree.changes_from(tree.basis_tree()).has_changed())
 +        self.assertEqual("data", open('dc/bl').read())
 +
 +    def test_rename_one(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        self.client_add("dc/bl")
 +        self.client_commit("dc", "Bla")
 +        tree = WorkingTree.open("dc")
 +        tree.rename_one("bl", "bloe")
 +        
 +        basis_inv = tree.basis_tree().inventory
 +        inv = tree.read_working_inventory()
 +        self.assertFalse(inv.has_filename("bl"))
 +        self.assertTrue(inv.has_filename("bloe"))
 +        self.assertEqual(basis_inv.path2id("bl"), 
 +                         inv.path2id("bloe"))
 +        self.assertIs(None, inv.path2id("bl"))
 +        self.assertIs(None, basis_inv.path2id("bloe"))
 +
 +    def test_empty_basis_tree(self):
 +        self.make_client('a', 'dc')
 +        wt = WorkingTree.open("dc")
 +        self.assertEqual(wt.branch.generate_revision_id(0), 
 +                         wt.basis_tree().inventory.revision_id)
 +        inv = Inventory()
 +        root_id = wt.branch.repository.get_mapping().generate_file_id(wt.branch.repository.uuid, 0, "", u"")
 +        inv.revision_id = wt.branch.generate_revision_id(0)
 +        inv.add_path('', 'directory', root_id).revision = inv.revision_id
 +                              
 +        self.assertEqual(inv, wt.basis_tree().inventory)
 +
 +    def test_basis_tree(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data"})
 +        self.client_add("dc/bl")
 +        self.client_commit("dc", "Bla")
 +        self.client_update("dc")
 +        tree = WorkingTree.open("dc")
 +        self.assertEqual(
 +            tree.branch.generate_revision_id(1),
 +            tree.basis_tree().get_revision_id())
 +
 +    def test_move(self):
 +        self.make_client('a', 'dc')
 +        self.build_tree({"dc/bl": "data", "dc/a": "data2", "dc/dir": None})
 +        self.client_add("dc/bl")
 +        self.client_add("dc/a")
 +        self.client_add("dc/dir")
 +        self.client_commit("dc", "Bla")
 +        tree = WorkingTree.open("dc")
 +        tree.move(["bl", "a"], "dir")
 +        
 +        basis_inv = tree.basis_tree().inventory
 +        inv = tree.read_working_inventory()
 +        self.assertFalse(inv.has_filename("bl"))
 +        self.assertFalse(inv.has_filename("a"))
 +        self.assertTrue(inv.has_filename("dir/bl"))
 +        self.assertTrue(inv.has_filename("dir/a"))
 +        mutter('basis: %r' % basis_inv.entries())
 +        mutter('working: %r' % inv.entries())
 +        self.assertFalse(inv.has_filename("bl"))
 +        self.assertFalse(basis_inv.has_filename("dir/bl"))
 +
 +    def test_get_parent_ids_no_merges(self)