Merge 0.4.
authorJelmer Vernooij <jelmer@samba.org>
Fri, 30 May 2008 02:30:03 +0000 (04:30 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Fri, 30 May 2008 02:30:03 +0000 (04:30 +0200)
21 files changed:
1  2 
NEWS
__init__.py
auth.py
branch.py
branchprops.py
commit.py
convert.py
errors.py
fetch.py
logwalker.py
mapping.py
remote.py
repository.py
setup.py
tests/test_commit.py
tests/test_convert.py
tests/test_errors.py
tests/test_repos.py
transport.py
tree.py
workingtree.py

diff --cc NEWS
index 596475147a2604ced17240d69beaf4d9a8610119,0f54c8d40622ace0f34385c1122bdfd7c0c5f76c..6b3ecac8dac4381c1f65475dedc5022170b2093e
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -29,20 -65,30 +65,36 @@@ bzr-svn 0.4.10  2008-05-1
  
     * Removed revision number cache.
  
+    * Switch to using "repository layouts" rather than branching schemes. 
+      Layouts are not part of the mapping so don't affect the revision identity.
    BUGS
  
-     * Avoid sometimes incorrect determination of Bazaar revision numbers. (#181773)
+    * Avoid sometimes incorrect determination of Bazaar revision numbers. (#181773)
+    * Deal with bzr:svn-revision-info property being removed. (#206728)
+    * Gracefully handle erroneous svk merge tickets created by old versions of svk.
+    * Use standard infrastructure for caching revision numbers. (#213953)
+    * Work around bug in the Subversion libraries which don't accept 
+      http(s) URLs with urlencoded characters. (#190229)
+    * Make sure committer name is UTF-8 encoded when overriding 
+      revision properties.
  
-       * Deal with bzr:svn-revision-info property being removed. (#206728)
+    * Fix installation of mapping3 package, don't rely on specific 
+      version of Python. (#227891)
  
- bzr-svn 0.4.9 2008-03-23
+ bzr-svn 0.4.9    2008-03-23
  
 +  CHANGES
 +
 +   * The Python-Subversion bindings are no longer necessary. Instead, 
 +     bzr-svn now comes with its own Python bindings for the Subversion 
 +       libraries.
 +
    FEATURES
  
     * Set revision properties when possible.
diff --cc __init__.py
Simple merge
diff --cc auth.py
index 3c2054d70dde20312f492c8f07ca636062152629,067d16798898e780bf0a80a0a53d105ba2679b55..555e4b5649729ee99ede7a5cb19cf49e69d69d21
+++ b/auth.py
  
  from bzrlib.config import AuthenticationConfig
  from bzrlib.ui import ui_factory
 -import svn.core
 -from svn.core import (svn_auth_cred_username_t, 
 -                      svn_auth_cred_simple_t,
 -                      svn_auth_cred_ssl_client_cert_t,
 -                      svn_auth_cred_ssl_client_cert_pw_t,
 -                      svn_auth_cred_ssl_server_trust_t,
 -                      svn_auth_get_username_prompt_provider,
 -                      svn_auth_get_simple_prompt_provider,
 -                      svn_auth_get_ssl_server_trust_prompt_provider,
 -                      svn_auth_get_ssl_client_cert_pw_prompt_provider,
 -                      svn_auth_open)
 +from ra import (get_username_prompt_provider,
 +                get_simple_prompt_provider,
 +                get_ssl_server_trust_prompt_provider,
 +                get_ssl_client_cert_pw_prompt_provider,
 +                get_simple_provider, get_username_provider, 
 +                get_ssl_client_cert_file_provider, 
 +                get_ssl_client_cert_pw_file_provider,
 +                get_ssl_server_trust_file_provider,
 +                Auth
 +                )
 +import ra
 +import constants
+ import urlparse
+ import urllib
  
 -
  class SubversionAuthenticationConfig(AuthenticationConfig):
      """Simple extended version of AuthenticationConfig that can provide 
      the information Subversion requires.
          super(SubversionAuthenticationConfig, self).__init__(file)
          self.scheme = scheme
          self.host = host
+         self.port = port
+         self.path = path
+        
 -    def get_svn_username(self, realm, may_save, pool=None):
 +    def get_svn_username(self, realm, may_save):
          """Look up a Subversion user name in the Bazaar authentication cache.
  
          :param realm: Authentication realm (optional)
          :param may_save: Whether or not the username should be saved.
 -        :param pool: Allocation pool, is ignored.
 -        :param default: Assumed username
          """
-         username = self.get_user(self.scheme, host=self.host, realm=realm)
 -        username_cred = svn_auth_cred_username_t()
 -        username_cred.username = self.get_user(self.scheme, 
 -                host=self.host, path=self.path, 
 -                realm=realm)
 -        username_cred.may_save = False
 -        return username_cred
++        username = self.get_user(self.scheme, host=self.host, path=self.path, realm=realm)
 +        return (username, False)
  
      def get_svn_simple(self, realm, username, may_save, pool):
          """Look up a Subversion user name+password combination in the Bazaar 
          :param may_save: Whether or not the username should be saved.
          :param pool: Allocation pool, is ignored.
          """
-         username = username or self.get_username(realm, may_save, 
-                                              pool, prompt="%s password" % realm)
 -        simple_cred = svn_auth_cred_simple_t()
 -        simple_cred.username = self.get_user(self.scheme, 
++        username = self.get_user(self.scheme, 
+                 host=self.host, path=self.path, realm=realm) or username
 -        simple_cred.password = self.get_password(self.scheme, host=self.host, 
 +        password = self.get_password(self.scheme, host=self.host, 
-                                     user=simple_cred.username, realm=realm,
-                                     prompt="%s password" % realm)
+             path=self.path, user=simple_cred.username, 
+             realm=realm, prompt="%s %s password" % (realm, simple_cred.username))
 -        simple_cred.may_save = False
 -        return simple_cred
 +        return (username, password, False)
  
      def get_svn_ssl_server_trust(self, realm, failures, cert_info, may_save, 
-                                  pool):
+                                      pool):
          """Return a Subversion auth provider that verifies SSL server trust.
  
          :param realm: Realm name (optional)
@@@ -130,33 -144,54 +133,53 @@@ def get_ssl_client_cert_pw(realm, may_s
  
  
  def get_ssl_client_cert_pw_provider(tries):
 -    return svn_auth_get_ssl_client_cert_pw_prompt_provider(
 +    return get_ssl_client_cert_pw_prompt_provider(
                  get_ssl_client_cert_pw, tries)
  
--
- def create_auth_baton():
-     """Create a Subversion authentication baton. """
-     # Give the client context baton a suite of authentication
-     # providers.h
-     providers = []
-     providers += SubversionAuthenticationConfig().get_svn_auth_providers()
-     providers += [
-         get_ssl_client_cert_pw_provider(1),
-         get_simple_provider(),
-         get_username_provider(),
-         get_ssl_client_cert_file_provider(),
-         get_ssl_client_cert_pw_file_provider(),
-         get_ssl_server_trust_file_provider(),
-         ]
+ def get_stock_svn_providers():
 -    providers = [svn.client.get_simple_provider(),
 -            svn.client.get_username_provider(),
 -            svn.client.get_ssl_client_cert_file_provider(),
 -            svn.client.get_ssl_client_cert_pw_file_provider(),
 -            svn.client.get_ssl_server_trust_file_provider(),
++    providers = [get_simple_provider(),
++            get_username_provider(),
++            get_ssl_client_cert_file_provider(),
++            get_ssl_client_cert_pw_file_provider(),
++            get_ssl_server_trust_file_provider(),
+             ]
  
 -    if hasattr(svn.client, 'get_windows_simple_provider'):
 -        providers.append(svn.client.get_windows_simple_provider())
 +    if hasattr(ra, 'get_windows_simple_provider'):
 +        providers.append(ra.get_windows_simple_provider())
  
 -    if hasattr(svn.client, 'get_keychain_simple_provider'):
 -        providers.append(svn.client.get_keychain_simple_provider())
 +    if hasattr(ra, 'get_keychain_simple_provider'):
 +        providers.append(ra.get_keychain_simple_provider())
  
 -    if hasattr(svn.client, 'get_windows_ssl_server_trust_provider'):
 -        providers.append(svn.client.get_windows_ssl_server_trust_provider())
 +    if hasattr(ra, 'get_windows_ssl_server_trust_provider'):
 +        providers.append(ra.get_windows_ssl_server_trust_provider())
  
 -    return providers
 +    return Auth(providers)
  
+ def create_auth_baton(url):
+     """Create an authentication baton for the specified URL."""
+     assert isinstance(url, str)
+     (scheme, netloc, path, _, _) = urlparse.urlsplit(url)
+     (creds, host) = urllib.splituser(netloc)
+     (host, port) = urllib.splitport(host)
+     auth_config = SubversionAuthenticationConfig(scheme, host, port, path)
+     # Specify Subversion providers first, because they use file data
+     # rather than prompting the user.
+     providers = get_stock_svn_providers()
+     if svn.core.SVN_VER_MAJOR == 1 and svn.core.SVN_VER_MINOR >= 5:
+         providers += auth_config.get_svn_auth_providers()
+         providers += [get_ssl_client_cert_pw_provider(1)]
+     auth_baton = svn.core.svn_auth_open(providers)
+     if creds is not None:
+         (auth_baton.user, auth_baton.password) = urllib.splitpasswd(creds)
+         if auth_baton.user is not None:
+             svn.core.svn_auth_set_parameter(auth_baton, 
+                 svn.core.SVN_AUTH_PARAM_DEFAULT_USERNAME, auth_baton.user)
+         if auth_baton.password is not None:
+             svn.core.svn_auth_set_parameter(auth_baton, 
+                 svn.core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, auth_baton.password)
+     return auth_baton
diff --cc branch.py
index 727847b0087d6b3238e0efa779ad7c1613db9581,ad95cee3f48ddf70e4bc72da703c2e3f1d34b783..6b13dca7429575a09ac31c9ca5a1052dc6895668
+++ b/branch.py
@@@ -21,12 -21,11 +21,12 @@@ from bzrlib.bzrdir import BzrDi
  from bzrlib.errors import (NoSuchFile, DivergedBranches, NoSuchRevision, 
                             NotBranchError)
  from bzrlib.inventory import (Inventory)
- from bzrlib.revision import ensure_null, NULL_REVISION
+ from bzrlib.revision import is_null, ensure_null, NULL_REVISION
  from bzrlib.workingtree import WorkingTree
  
 -import svn.client, svn.core
 -from svn.core import SubversionException
 +import core
 +from core import SubversionException
 +import constants
  
  from commit import push
  from config import BranchConfig
@@@ -175,18 -174,23 +175,19 @@@ class SvnBranch(Branch)
          :param revision_id: Tip of the checkout.
          :return: WorkingTree object of the checkout.
          """
 -        peg_rev = svn.core.svn_opt_revision_t()
 -        peg_rev.kind = svn.core.svn_opt_revision_head
 -
 -        rev = svn.core.svn_opt_revision_t()
 -        if revision_id is None:
 -            rev.kind = svn.core.svn_opt_revision_head
 -        else:
 +        import os, wc
 +        if revision_id is not None:
              revnum = self.lookup_revision_id(revision_id)
 -            rev.kind = svn.core.svn_opt_revision_number
 -            rev.value.number = revnum
 +        else:
 +            revnum = self.get_revnum()
  
 -        client_ctx = create_svn_client(svn_url)
 -        svn.client.checkout(svn_url, to_location, rev, 
 -                            True, client_ctx)
 -
 -        return WorkingTree.open(to_location)
 +        os.mkdir(to_location)
+         svn_url = bzr_to_svn_url(self.base)
-                       bzr_to_svn_url(self.repository.base), revnum)
 +        wc.ensure_adm(to_location, self.repository.uuid, bzr_to_svn_url(self.base),
++                      svn_url, revnum)
 +        wt = WorkingTree.open(to_location)
 +        wt.update(["."], revnum=revnum)
 +        return wt
  
      def create_checkout(self, to_location, revision_id=None, lightweight=False,
                          accelerator_tree=None, hardlink=False):
diff --cc branchprops.py
index f423dae9d769803db3c460522f17b6c186e59563,56cdc72ca830abf223224c2986600f7ef2d8d004..832fb7788ac29dc7134d3b7a679878d6f412c82c
@@@ -38,10 -38,10 +38,10 @@@ class PathPropertyProvider(object)
          path = path.lstrip("/")
  
          try:
-             (_, _, props) = self.log._get_transport().get_dir(path, 
+             (_, _, props) = self.log._transport.get_dir(path, 
                  revnum)
          except SubversionException, (_, num):
 -            if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
 +            if num == constants.ERR_FS_NO_SUCH_REVISION:
                  raise NoSuchRevision(self, revnum)
              raise
  
diff --cc commit.py
index d2e20a08700d55b11cf4829330ba0f56ed48e1fc,b7f0367dfe02bc9f45c4ce479130d0c58ab17769..400df637b07c89bc54d54a577f17f19a81f62a80
+++ b/commit.py
@@@ -28,15 -28,15 +28,17 @@@ from bzrlib.repository import RootCommi
  from bzrlib.revision import NULL_REVISION
  from bzrlib.trace import mutter, warning
  
+ from bzrlib.plugins.svn import util
  from cStringIO import StringIO
  from errors import ChangesRootLHSHistory, MissingPrefix, RevpropChangeFailed
 +from ra import txdelta_send_stream
  from svk import (generate_svk_feature, serialize_svk_features, 
                   parse_svk_features, SVN_PROP_SVK_MERGE)
- from mapping import parse_revision_id
- from repository import (SvnRepositoryFormat, SvnRepository, lazy_dict)
- from scheme import is_valid_property_name
 +import constants
+ from logwalker import lazy_dict
+ from bzrlib.plugins.svn.mapping import parse_revision_id
+ from repository import SvnRepositoryFormat, SvnRepository
  import urllib
  
  
@@@ -239,9 -242,8 +243,7 @@@ class SvnCommitBuilder(RootCommitBuilde
              # 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(
-                     urlutils.join(self.branch.get_branch_path(), 
-                                   new_child_path))
 -                child_baton = self.editor.add_file(
 -                    full_new_child_path, baton, None, -1, self.pool)
++                child_editor = dir_editor.add_file(full_new_child_path)
  
  
              # copy if they existed at different location
                  self.mutter('copy %s %r -> %r' % (child_ie.kind, 
                                    self.old_inv.id2path(child_ie.file_id), 
                                    new_child_path))
 -                child_baton = self.editor.add_file(
 +                child_editor = dir_editor.add_file(
-                     urlutils.join(self.branch.get_branch_path(), new_child_path), 
+                         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, self.pool)
 +                    self.base_revnum)
  
              # open if they existed at the same location
              elif child_ie.revision is None:
                  self.mutter('open %s %r' % (child_ie.kind, new_child_path))
  
-                 child_editor = dir_editor.open_file(
-                     urlutils.join(self.branch.get_branch_path(), 
-                         new_child_path), self.base_revnum)
 -                child_baton = self.editor.open_file(
 -                        full_new_child_path, baton, self.base_revnum, self.pool)
++                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
          """Finish the commit.
  
          """
 -        def done(revision_data, pool):
 +        def done(revision, author, date):
              """Callback that is called by the Subversion commit editor 
              once the commit finishes.
 -
 -            :param revision_data: Revision metadata
              """
 -            self.revision_metadata = revision_data
 -        
 +
-             # Make sure the logwalker doesn't try to use ra 
-             # during checkouts...
-             self.repository._log.fetch_revisions(revision)
-             revid = self.branch.generate_revision_id(revision)
-             self.mutter('commit %d finished. author: %r, date: %r, revid: %r' % 
-                (revision, author, date, revid))
-             assert self._new_revision_id is None or self._new_revision_id == revid
-             override_svn_revprops = self.repository.get_config().get_override_svn_revprops()
-             if override_svn_revprops is not None:
-                 new_revprops = {}
-                 if svn.core.SVN_PROP_REVISION_AUTHOR in override_svn_revprops:
-                     new_revprops[svn.core.SVN_PROP_REVISION_AUTHOR] = self._committer
-                 if svn.core.SVN_PROP_REVISION_DATE in override_svn_revprops:
-                     new_revprops[svn.core.SVN_PROP_REVISION_DATE] = svn_time_to_cstring(1000000*self._timestamp)
-                 set_svn_revprops(self.repository.transport, self.revision_metadata.revision, new_revprops)
-             try:
-                 set_svn_revprops(self.repository.transport, revision, 
-                              self._svn_revprops) 
-             except RevpropChangeFailed:
-                 pass # Ignore for now
-             self.revid = revid
++            self.revision_metadata = object()
++            self.revision_metadata.revision = revision
++            self.revision_metadata.author = author
++            self.revision_metadata.date = date
 +
          bp_parts = self.branch.get_branch_path().split("/")
-         repository_latest_revnum = self.repository.transport.get_latest_revnum()
+         repository_latest_revnum = self.repository.get_latest_revnum()
          lock = self.repository.transport.lock_write(".")
-         set_revprops = self.repository.get_config().get_set_revprops()
+         set_revprops = self._config.get_set_revprops()
          remaining_revprops = self._svn_revprops # Keep track of the revprops that haven't been set yet
  
          # Store file ids
              fileids[path] = id
  
          self.base_mapping.export_fileid_map(fileids, self._svn_revprops, self._svnprops)
-         if self.repository.get_config().get_log_strip_trailing_newline():
+         if self._config.get_log_strip_trailing_newline():
              self.base_mapping.export_message(message, self._svn_revprops, self._svnprops)
              message = message.rstrip("\n")
 -        self._svn_revprops[svn.core.SVN_PROP_REVISION_LOG] = message.encode("utf-8")
 +        self._svn_revprops[constants.SVN_PROP_REVISION_LOG] = message.encode("utf-8")
  
          try:
              existing_bp_parts = _check_dirs_exist(self.repository.transport, 
                                                bp_parts, -1)
 -            self.revision_metadata = None
              for prop in self._svn_revprops:
-                 if not is_valid_property_name(prop):
+                 if not util.is_valid_property_name(prop):
                      warning("Setting property %r with invalid characters in name" % prop)
              try:
                  self.editor = self.repository.transport.get_commit_editor(
diff --cc convert.py
index 24d7a0c41a894757c3a9d018ca9732dc8df054a1,73fe0c2e329d9b815f341acd81d72f26c0415810..3ad6ad5fde8e4977fb9e1955a5f9cebf18f87461
@@@ -71,15 -71,16 +71,15 @@@ def load_dumpfile(dumpfile, outputdir)
      else:
          file = open(dumpfile)
      try:
 -        svn.repos.load_fs2(repos, file, StringIO(), 
 -                svn.repos.load_uuid_default, '', 0, 0, None)
 -    except svn.core.SubversionException, (_, num):
 -        if num == svn.core.SVN_ERR_STREAM_MALFORMED_DATA:
 +        r.load_fs(file, StringIO(), repos.LOAD_UUID_DEFAULT)
 +    except core.SubversionException, (_, num):
 +        if num == constants.ERR_STREAM_MALFORMED_DATA:
              raise NotDumpFile(dumpfile)
          raise
 -    return repos
 +    return r
  
  
- def convert_repository(source_repos, output_url, scheme=None, 
+ def convert_repository(source_repos, output_url, scheme=None, layout=None,
                         create_shared_repo=True, working_trees=False, all=False,
                         format=None, filter_branch=None):
      """Convert a Subversion repository and its' branches to a 
diff --cc errors.py
Simple merge
diff --cc fetch.py
index 56a42c80b70f63f01e31a5bf64964bccf89a7ed6,5bf69f7be0b69e4b000f422e506e6048596ff458..a907cbbce5946c0178842e7330f3131779e96418
+++ b/fetch.py
@@@ -25,19 -25,21 +25,20 @@@ from bzrlib.trace import mutte
  from cStringIO import StringIO
  import md5
  
 -from svn.core import Pool
 -import svn.core
 +import constants
  
  from bzrlib.plugins.svn.errors import InvalidFileName
- from mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_MERGE, 
+ from logwalker import lazy_dict
+ from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_MERGE, 
                       SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO, 
-                      SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_REVISION_ID,
+                      SVN_PROP_BZR_REVISION_ID,
                       SVN_PROP_BZR_FILEIDS, SVN_REVPROP_BZR_SIGNATURE,
                       parse_merge_property,
                       parse_revision_metadata)
- from repository import (SvnRepository, SvnRepositoryFormat, lazy_dict)
+ from repository import SvnRepository, SvnRepositoryFormat
  from svk import SVN_PROP_SVK_MERGE
 -from tree import (apply_txdelta_handler, parse_externals_description, 
 -                  inventory_add_external)
 +from delta import apply_txdelta_handler
 +from tree import (parse_externals_description, inventory_add_external)
  
  
  def _escape_commit_message(message):
@@@ -92,29 -94,29 +93,31 @@@ class RevisionBuildEditor
          self.source = source
          self.transact = target.get_transaction()
  
-     def start_revision(self, revid, prev_inventory):
 +    def set_target_revision(self, target_revision):
 +        pass
 +
+     def start_revision(self, revid, prev_inventory, revmeta):
          self.revid = revid
          (self.branch_path, self.revnum, self.mapping) = self.source.lookup_revision_id(revid)
-         self.svn_revprops = self.source._log._get_transport().revprop_list(self.revnum)
-         changes = self.source._log.get_revision_paths(self.revnum, self.branch_path)
-         fileprops = lazy_dict(self.source.branchprop_list.get_changed_properties, self.branch_path, self.revnum)
-         renames = self.mapping.import_fileid_map(self.svn_revprops, fileprops)
-         self.id_map = self.source.transform_fileid_map(self.source.uuid, 
-                               self.revnum, self.branch_path, changes, renames, 
-                               self.mapping)
-         self._bzr_merges = ()
-         self._svk_merges = ()
+         self.revmeta = revmeta
+         self._id_map = None
+         self.dir_baserev = {}
+         self._revinfo = None
          self._premature_deletes = set()
 -        self.pool = Pool()
          self.old_inventory = prev_inventory
          self.inventory = prev_inventory.copy()
-         self._branch_fileprops = {}
          self._start_revision()
  
-     def _get_parent_ids(self):
-         return self.source.revision_parents(self.revid, self._branch_fileprops)
+     def _get_id_map(self):
+         if self._id_map is not None:
+             return self._id_map
+         renames = self.mapping.import_fileid_map(self.revmeta.revprops, self.revmeta.fileprops)
+         self._id_map = self.source.transform_fileid_map(self.source.uuid, 
+                               self.revnum, self.branch_path, self.revmeta.paths, renames, 
+                               self.mapping)
+         return self._id_map
  
      def _get_revision(self, revid):
          """Creates the revision object.
@@@ -262,52 -247,58 +264,34 @@@ class DirectoryBuildEditor
              for name in ie.children:
                  ie.children[name].parent_id = file_id
              # FIXME: Don't touch inventory internals
 -            del self.inventory._byid[base_file_id]
 -            self.inventory._byid[file_id] = ie
 +            del self.editor.inventory._byid[base_file_id]
 +            self.editor.inventory._byid[file_id] = ie
              ie.file_id = file_id
 -            self.dir_baserev[file_id] = []
 -        ie.revision = self.revid
 -        return (base_file_id, file_id)
 +            file_parents = []
 +        ie.revision = self.editor.revid
 +        return DirectoryBuildEditor(self.editor, base_file_id, file_id, 
 +                                    file_parents)
  
 -    def change_dir_prop(self, (old_id, new_id), name, value, pool):
 -        if new_id == self.inventory.root.file_id:
 +    def change_prop(self, name, value):
 +        if self.new_id == self.editor.inventory.root.file_id:
-             self.editor._branch_fileprops[name] = value
-         if name == SVN_PROP_BZR_BRANCHING_SCHEME:
-             if self.new_id != self.editor.inventory.root.file_id:
-                 mutter('rogue %r on non-root directory' % name)
-                 return
-         elif name == SVN_PROP_BZR_ANCESTRY+str(self.editor.mapping.scheme):
-             if self.new_id != self.editor.inventory.root.file_id:
-                 mutter('rogue %r on non-root directory' % name)
-                 return
-             
-             self.editor._bzr_merges = parse_merge_property(value.splitlines()[-1])
-         elif name == SVN_PROP_SVK_MERGE:
-             self.editor._svk_merges = None # Force Repository.revision_parents() to look it up
-         elif name == SVN_PROP_BZR_REVISION_INFO:
-             if self.new_id != self.editor.inventory.root.file_id:
-                 mutter('rogue %r on non-root directory' % SVN_PROP_BZR_REVISION_INFO)
-                 return
-  
-         elif name in (constants.PROP_ENTRY_COMMITTED_DATE,
-                       constants.PROP_ENTRY_COMMITTED_REV,
-                       constants.PROP_ENTRY_LAST_AUTHOR,
-                       constants.PROP_ENTRY_LOCK_TOKEN,
-                       constants.PROP_ENTRY_UUID,
-                       constants.PROP_EXECUTABLE,
-                       SVN_PROP_BZR_MERGE, SVN_PROP_BZR_FILEIDS):
+             # Replay lazy_dict, since it may be more expensive
+             if type(self.revmeta.fileprops) != dict:
+                 self.revmeta.fileprops = {}
+             self.revmeta.fileprops[name] = value
 -        if name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
 -                      svn.core.SVN_PROP_ENTRY_COMMITTED_REV,
 -                      svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
 -                      svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
 -                      svn.core.SVN_PROP_ENTRY_UUID,
 -                      svn.core.SVN_PROP_EXECUTABLE):
++        if name in (constants.PROP_ENTRY_COMMITTED_DATE,
++                    constants.PROP_ENTRY_COMMITTED_REV,
++                    constants.PROP_ENTRY_LAST_AUTHOR,
++                    constants.PROP_ENTRY_LOCK_TOKEN,
++                    constants.PROP_ENTRY_UUID,
++                    constants.PROP_EXECUTABLE):
              pass
-         elif (name.startswith(SVN_PROP_BZR_ANCESTRY) or 
-               name.startswith(SVN_PROP_BZR_REVISION_ID) or 
-               name.startswith(constants.PROP_WC_PREFIX)):
 -        elif (name.startswith(svn.core.SVN_PROP_WC_PREFIX)):
++        elif (name.startswith(constants.PROP_WC_PREFIX)):
              pass
-         elif (name.startswith(constants.PROP_PREFIX) or
-               name.startswith(SVN_PROP_BZR_PREFIX)):
 -        elif name.startswith(svn.core.SVN_PROP_PREFIX):
++        elif name.startswith(constants.PROP_PREFIX):
              mutter('unsupported dir property %r' % name)
  
 -    def change_file_prop(self, id, name, value, pool):
 -        if name == svn.core.SVN_PROP_EXECUTABLE: 
 -            # You'd expect executable to match 
 -            # svn.core.SVN_PROP_EXECUTABLE_VALUE, but that's not 
 -            # how SVN behaves. It appears to consider the presence 
 -            # of the property sufficient to mark it executable.
 -            self.is_executable = (value != None)
 -        elif (name == svn.core.SVN_PROP_SPECIAL):
 -            self.is_symlink = (value != None)
 -        elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
 -            self.last_file_rev = int(value)
 -        elif name == svn.core.SVN_PROP_EXTERNALS:
 -            mutter('svn:externals property on file!')
 -        elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
 -                      svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
 -                      svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
 -                      svn.core.SVN_PROP_ENTRY_UUID,
 -                      svn.core.SVN_PROP_MIME_TYPE):
 -            pass
 -        elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
 -            pass
 -        elif (name.startswith(svn.core.SVN_PROP_PREFIX) or
 -              name.startswith(SVN_PROP_BZR_PREFIX)):
 -            mutter('unsupported file property %r' % name)
 -
 -    def add_file(self, path, (old_parent_id, new_parent_id), copyfrom_path, copyfrom_revnum, baton):
 +    def add_file(self, path, copyfrom_path=None, copyfrom_revnum=-1):
          assert isinstance(path, str)
          path = path.decode("utf-8")
          check_filename(path)
@@@ -593,58 -582,16 +592,66 @@@ class InterFromSvnRepository(InterRepos
          """See InterRepository.copy_content."""
          self.fetch(revision_id, pb, find_ghosts=False)
  
 -    def _fetch_replay(self, revids, pb=None):
 -        """Copy a set of related revisions using svn.ra.replay.
 +    def _fetch_revision(self, editor, transport, repos_root, parent_revid):
 +        if self._supports_replay:
 +            try:
 +                self._fetch_revision_replay(editor, transport, repos_root, parent_revid)
 +                return
 +            except NotImplementedError:
 +                self._supports_replay = False
 +        self._fetch_revision_update(editor, transport, repos_root, parent_revid)
 +
 +    def _fetch_revision_replay(self, editor, transport, repos_root, parent_revid):
 +        if parent_revid is not None:
 +            parent_revnum = self.source.lookup_revision_id(parent_revid)[1]
 +        else:
 +            parent_revnum = editor.revnum-1
 +        branch_url = urlutils.join(repos_root, editor.branch_path)
 +        transport.reparent(branch_url)
 +        lock = transport.lock_read(".")
 +        try:
 +            transport.replay(editor.revnum, parent_revnum, editor, True)
 +        finally:
 +            lock.unlock()
 +
 +    def _fetch_revision_update(self, editor, transport, repos_root, parent_revid):
-         if parent_revid == NULL_REVISION:
-             branch_url = urlutils.join(repos_root, editor.branch_path)
-             transport.reparent(branch_url)
-             assert transport.svn_url == branch_url.rstrip("/"), \
-                 "Expected %r, got %r" % (transport.svn_url, branch_url)
-             reporter = transport.do_update(editor.revnum, True, editor)
-             # Report status of existing paths
-             reporter.set_path("", editor.revnum, True)
-         else:
-             (parent_branch, parent_revnum, mapping) = \
-                     self.source.lookup_revision_id(parent_revid)
-             transport.reparent(urlutils.join(repos_root, parent_branch))
++            if parent_revid == NULL_REVISION:
++                branch_url = urlutils.join(repos_root, 
++                                           editor.branch_path)
++                conn.reparent(branch_url)
++                assert conn.url == branch_url, \
++                    "Expected %r, got %r" % (conn.url, branch_url)
++                reporter = conn.do_update(editor.revnum, True, editor)
  
-             if parent_branch != editor.branch_path:
-                 reporter = transport.do_switch(editor.revnum, True, 
-                     urlutils.join(repos_root, editor.branch_path), editor)
 -        :param revids: Revision ids to copy.
 -        :param pb: Optional progress bar
 -        """
 -        raise NotImplementedError(self._copy_revisions_replay)
++                try:
++                    # Report status of existing paths
++                    reporter.set_path("", editor.revnum, True, None)
++                except:
++                    reporter.abort()
++                    raise
 +            else:
-                 reporter = transport.do_update(editor.revnum, True, editor)
++                (parent_branch, parent_revnum, mapping) = \
++                        self.source.lookup_revision_id(parent_revid)
++                conn.reparent(urlutils.join(repos_root, parent_branch))
++
++                if parent_branch != editor.branch_path:
++                    reporter = conn.do_switch(editor.revnum, True, 
++                        urlutils.join(repos_root, editor.branch_path), 
++                        editor)
++                else:
++                    reporter = conn.do_update(editor.revnum, True, editor)
 +
-             # Report status of existing paths
-             reporter.set_path("", parent_revnum, False)
++                try:
++                    # Report status of existing paths
++                    reporter.set_path("", parent_revnum, False)
++                except:
++                    reporter.abort()
++                    raise
 +
-         lock = transport.lock_read(".")
-         reporter.finish()
-         lock.unlock()
++            reporter.finish()
  
-     def _fetch_switch(self, revids, pb=None):
-         """Copy a set of related revisions using ra_switch.
+     def _fetch_switch(self, conn, revids, pb=None):
+         """Copy a set of related revisions using svn.ra.switch.
  
          :param revids: List of revision ids of revisions to copy, 
                         newest first.
                  else:
                      parent_inv = prev_inv
  
-                 editor.start_revision(revid, parent_inv)
+                 editor.start_revision(revid, parent_inv, revmeta)
  
                  try:
 -                    pool = Pool()
 -
 -                    if parent_revid == NULL_REVISION:
 -                        branch_url = urlutils.join(repos_root, 
 -                                                   editor.branch_path)
 -                        conn.reparent(branch_url)
 -                        assert conn.url == branch_url, \
 -                            "Expected %r, got %r" % (conn.url, branch_url)
 -                        reporter = conn.do_update(editor.revnum, True, 
 -                                                       editor, pool)
 -
 -                        try:
 -                            # Report status of existing paths
 -                            reporter.set_path("", editor.revnum, True, None, pool)
 -                        except:
 -                            reporter.abort_report(pool)
 -                            raise
 -                    else:
 -                        (parent_branch, parent_revnum, mapping) = \
 -                                self.source.lookup_revision_id(parent_revid)
 -                        conn.reparent(urlutils.join(repos_root, parent_branch))
 -
 -                        if parent_branch != editor.branch_path:
 -                            reporter = conn.do_switch(editor.revnum, True, 
 -                                urlutils.join(repos_root, editor.branch_path), 
 -                                editor, pool)
 -                        else:
 -                            reporter = conn.do_update(editor.revnum, True, editor)
 -
 -                        try:
 -                            # Report status of existing paths
 -                            reporter.set_path("", parent_revnum, False, None, pool)
 -                        except:
 -                            reporter.abort_report(pool)
 -                            raise
 -
 -                    reporter.finish_report(pool)
++<<<<<<< TREE
 +                    self._fetch_revision_update(editor, transport, repos_root, parent_revid)
++=======
++>>>>>>> MERGE-SOURCE
                  except:
 -                    editor.abort_edit()
 +                    editor.abort()
                      raise
  
                  prev_inv = editor.inventory
          """Fetch revisions. """
          if revision_id == NULL_REVISION:
              return
 +
 +        self._supports_replay = True # assume replay supported by default
          # Dictionary with paths as keys, revnums as values
  
+         if pb:
+             pb.update("determining revisions to fetch", 0, 2)
          # Loop over all the revnums until revision_id
          # (or youngest_revnum) and call self.target.add_revision() 
          # or self.target.add_inventory() each time
diff --cc logwalker.py
index db4d66d63bbac59b0db69af032690126a2e5aed6,cecc2cf1addf0510f13116be898813e4e7ad562d..6708e0ecf433982f807d8d394e0cbfdd374012f3
@@@ -20,12 -20,12 +20,13 @@@ from bzrlib.errors import NoSuchRevisio
  from bzrlib.trace import mutter
  import bzrlib.ui as ui
  
 -from svn.core import SubversionException, Pool
 +from core import SubversionException
  from transport import SvnRaTransport
 -import svn.core
 +import core
 +import constants
  
  from cache import CacheTable
+ import changes
  
  LOG_CHUNK_LIMIT = 0
  
@@@ -319,72 -391,69 +392,68 @@@ class LogWalker(object)
          :param path:  Path to check
          :param revnum:  Revision to check
          """
+         assert isinstance(path, str), "invalid path"
          path = path.strip("/")
-         transport = self._get_transport()
-         ft = transport.check_path(path, revnum)
-         if ft == core.NODE_FILE:
-             return []
-         assert ft == core.NODE_DIR
-         class DirLister:
-             def __init__(self, base, files):
-                 self.files = files
+         conn = self._transport.connections.get(self._transport.get_svn_repos_root())
+         try:
+             ft = conn.check_path(path, revnum)
+             if ft == svn.core.svn_node_file:
+                 return []
+             assert ft == svn.core.svn_node_dir
+         finally:
+             self._transport.connections.add(conn)
+         class TreeLister(svn.delta.Editor):
+             def __init__(self, base):
+                 self.files = []
                  self.base = base
  
-             def change_prop(self, name, value):
+             def set_target_revision(self, revnum):
+                 """See Editor.set_target_revision()."""
                  pass
  
-             def add_directory(self, path):
-                 """See Editor.add_directory()."""
-                 self.files.append(urlutils.join(self.base, path))
-                 return DirLister(self.base, self.files)
 -            def open_root(self, revnum, baton):
++            def open_root(self, revnum):
+                 """See Editor.open_root()."""
+                 return path
  
-             def add_file(self, path):
+             def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
+                 """See Editor.add_directory()."""
                  self.files.append(urlutils.join(self.base, path))
-                 return FileLister()
+                 return path
  
-             def close(self):
+             def change_dir_prop(self, id, name, value, pool):
                  pass
  
-         class FileLister:
-             def __init__(self):
+             def change_file_prop(self, id, name, value, pool):
                  pass
  
-             def change_prop(self, name, value):
-                 pass
+             def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
+                 self.files.append(urlutils.join(self.base, path))
+                 return path
  
-             def apply_textdelta(self, base_checksum=None):
+             def close_dir(self, id):
                  pass
  
-             def close(self, checksum=None):
+             def close_file(self, path, checksum):
                  pass
  
-         class TreeLister:
-             def __init__(self, base):
-                 self.files = []
-                 self.base = base
-             def set_target_revision(self, revnum):
-                 """See Editor.set_target_revision()."""
+             def close_edit(self):
                  pass
  
-             def open_root(self, revnum):
-                 """See Editor.open_root()."""
-                 return DirLister(self.base, self.files)
-             def close(self, checksum=None):
+             def abort_edit(self):
                  pass
  
-             def abort(self):
+             def apply_textdelta(self, file_id, base_checksum):
                  pass
  
 -        pool = Pool()
          editor = TreeLister(path)
-         old_base = transport.base
-         root_repos = transport.get_svn_repos_root()
-         reporter = transport.do_switch(revnum, True, 
-                 urlutils.join(root_repos, path), editor)
-         reporter.set_path("", 0, True)
-         reporter.finish()
+         try:
+             conn = self._transport.connections.get(urlutils.join(self._transport.get_svn_repos_root(), path))
+             reporter = conn.do_update(revnum, True, editor, pool)
+             reporter.set_path("", revnum, True, None, pool)
+             reporter.finish_report(pool)
+         finally:
+             self._transport.connections.add(conn)
          return editor.files
  
      def get_previous(self, path, revnum):
diff --cc mapping.py
index b8eb098fbb03b910c7d72956fb59fb68bfdd229e,07f711b9eeb13d8e6565438b37bac5755649936f..0ca214af685b9df87678fea6516791ba3c055498
@@@ -19,14 -19,12 +19,15 @@@ from bzrlib import osutils, registr
  from bzrlib.errors import InvalidRevisionId
  from bzrlib.trace import mutter
  
+ from bzrlib.plugins.svn import version_info
  import calendar
 +import core
  import errors
 -import svn
 +from scheme import BranchingScheme, guess_scheme_from_branch_path
 +import sha
  import time
  import urllib
 +import constants
  
  MAPPING_VERSION = 3
  
diff --cc remote.py
index e37dddeb3174685cbf9c3febb34b2da4a77ae2e7,916793eb0326765fe8a9ce32f13581d37f32864f..75eb07e867ace7627158178a0b94cd9eefa95554
+++ b/remote.py
@@@ -140,14 -140,22 +140,22 @@@ class SvnRemoteAccess(BzrDir)
              stop_revision = source.last_revision()
          target_branch_path = self.branch_path.strip("/")
          repos = self.find_repository()
-         full_branch_url = urlutils.join(repos.transport.base, 
-                                         target_branch_path)
-         if repos.transport.check_path(target_branch_path,
-             repos.transport.get_latest_revnum()) != core.NODE_NONE:
-             raise AlreadyBranchError(full_branch_url)
-         push_new(repos, target_branch_path, source, stop_revision)
+         repos.lock_write()
+         try:
+             full_branch_url = urlutils.join(repos.transport.base, 
+                                             target_branch_path)
+             if repos.transport.check_path(target_branch_path,
 -                repos.get_latest_revnum()) != svn.core.svn_node_none:
++                repos.get_latest_revnum()) != core.NODE_NONE:
+                 raise AlreadyBranchError(full_branch_url)
+             push_new(repos, target_branch_path, source, stop_revision)
+         finally:
+             repos.unlock()
          branch = self.open_branch()
-         branch.pull(source, stop_revision=stop_revision)
+         branch.lock_write()
+         try:
+             branch.pull(source, stop_revision=stop_revision)
+         finally:
+             branch.unlock()
          return branch
  
      def create_branch(self):
diff --cc repository.py
index 7f58e4ae59dd5b54833c53137c3f7703253b3934,8c8d9707cd4e13e4220c29e05f395de1c3ae8fd6..6416d95c48f10d4a9e79322a87a32445abf40158
@@@ -788,12 -756,12 +757,12 @@@ class SvnRepository(Repository)
                                  try:
                                      for c in self.transport.get_dir(p, i)[0].keys():
                                          n = p+"/"+c
-                                         if scheme.is_branch(n) or scheme.is_tag(n):
+                                         if layout.is_branch(n) or layout.is_tag(n):
                                              created_branches[n] = i
-                                         elif (scheme.is_branch_parent(n) or 
-                                               scheme.is_tag_parent(n)):
+                                         elif (layout.is_branch_parent(n) or 
+                                               layout.is_tag_parent(n)):
                                              parents.append(n)
 -                                except SubversionException, (_, svn.core.SVN_ERR_FS_NOT_DIRECTORY):
 +                                except SubversionException, (_, constants.ERR_FS_NOT_DIRECTORY):
                                      pass
          finally:
              pb.finished()
diff --cc setup.py
index d936f704f404f6b4e63689e74257e6855001021c,e6a277259fbe9bc1fdcfcffd2d205f031a712c7d..84d7c559d7fec0ffa81409f30139ef7aae74ea75
+++ b/setup.py
@@@ -1,27 -1,6 +1,27 @@@
- #!/usr/bin/env python2.4
+ #!/usr/bin/env python
 +# Setup file for bzr-svn
 +# Copyright (C) 2005-2008 Jelmer Vernooij <jelmer@samba.org>
  
  from distutils.core import setup
 +from distutils.extension import Extension
 +from Pyrex.Distutils import build_ext
 +import os
 +
 +def apr_include_dir():
 +    """Determine the APR header file location."""
 +    f = os.popen("apr-config --includedir")
 +    dir = f.read().rstrip("\n")
 +    if not os.path.isdir(dir):
 +        raise Exception("APR development headers not found")
 +    return dir
 +
 +def svn_include_dir():
 +    """Determine the Subversion header file location."""
 +    dirs = ["/usr/local/include/subversion-1", "/usr/include/subversion-1"]
 +    for dir in dirs:
 +        if os.path.isdir(dir):
 +            return dir
 +    raise Exception("Subversion development headers not found")
  
  setup(name='bzr-svn',
        description='Support for Subversion branches in Bazaar',
        package_dir={'bzrlib.plugins.svn':'.', 
                     'bzrlib.plugins.svn.tests':'tests'},
        packages=['bzrlib.plugins.svn', 
 -                'bzrlib.plugins.svn.tests']
+                 'bzrlib.plugins.svn.mapping3', 
 +                'bzrlib.plugins.svn.tests'],
 +      ext_modules=[
 +          Extension("core", ["core.pyx"], libraries=["svn_subr-1"], 
 +                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
 +          Extension("client", ["client.pyx"], libraries=["svn_client-1"], 
 +                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
 +          Extension("ra", ["ra.pyx"], libraries=["svn_ra-1"], 
 +                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
 +          Extension("repos", ["repos.pyx"], libraries=["svn_repos-1"], 
 +                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
 +          Extension("wc", ["wc.pyx"], libraries=["svn_wc-1"],
 +                     include_dirs=[apr_include_dir(), svn_include_dir()])],
 +      cmdclass = {'build_ext': build_ext},
        )
index b6db48f80632f3750fe65af3110d314a027429ab,d1266a9838dab59b709fd919f147c262005659eb..0bd2adb8d73cf4bb9eb2321e8742be54d37d0769
@@@ -29,10 -29,10 +29,10 @@@ from commit import set_svn_revprops, _r
  from copy import copy
  from errors import RevpropChangeFailed
  import os
- from remote import SvnRaTransport
+ from transport import SvnRaTransport
  from tests import TestCaseWithSubversionRepository
  
 -from svn.core import svn_time_to_cstring
 +from core import time_to_cstring
  
  class TestNativeCommit(TestCaseWithSubversionRepository):
      def test_simple_commit(self):
index 1e0299b811bb32c73053de812345f0615a381730,9af57aafe3d2134f6a4a2be7c4b895789acca7d4..ec06e81d45b98ddb8d3e90b1a7d35a99c896c9fc
@@@ -27,10 -27,11 +27,11 @@@ from bzrlib.trace import mutte
  import os, sys
  from convert import convert_repository, NotDumpFile, load_dumpfile
  from format import get_rich_root_format
- from scheme import TrunkBranchingScheme, NoBranchingScheme
+ from mapping3 import set_branching_scheme
+ from mapping3.scheme import TrunkBranchingScheme, NoBranchingScheme
  from tests import TestCaseWithSubversionRepository
  
 -import svn.repos
 +import repos
  
  class TestLoadDumpfile(TestCaseInTempDir):
      def test_loaddumpfile(self):
Simple merge
index 44a63362e89ec337082a0b87aa3b814e6bdab460,9ae66a9ab6bfce9611d731e08c78f5e37cc51c19..df30ac38a5e9de1674815e7cf2900b8f55c0c8e8
@@@ -31,10 -31,14 +31,12 @@@ from bzrlib.tests import TestCas
  
  import os, sys
  
 -import svn.fs, svn
 -
  import format
  from mapping import (escape_svn_path, unescape_svn_path, 
-                      SVN_PROP_BZR_REVISION_ID, SVN_PROP_BZR_BRANCHING_SCHEME)
- from scheme import (TrunkBranchingScheme, NoBranchingScheme, 
+                      SVN_PROP_BZR_REVISION_ID)
+ from mapping3 import (SVN_PROP_BZR_BRANCHING_SCHEME, set_branching_scheme,
+                       set_property_scheme, BzrSvnMappingv3)
+ from mapping3.scheme import (TrunkBranchingScheme, NoBranchingScheme, 
                      ListBranchingScheme, SingleBranchingScheme)
  from transport import SvnRaTransport
  from tests import TestCaseWithSubversionRepository
diff --cc transport.py
index e4006608b026cd1926fc412f85b65312b9e8efe2,9b070e805d5ede1c81aabbffaa448116f867d4c1..832fada02bf3a9ef0ab38621fe8a0899f2ea68e2
@@@ -21,21 -21,30 +21,22 @@@ from bzrlib.errors import (NoSuchFile, 
  from bzrlib.trace import mutter
  from bzrlib.transport import Transport
  
 -from svn.core import SubversionException, Pool
 -import svn.ra
 -import svn.core
 -import svn.client
 +from core import SubversionException
 +from auth import create_auth_baton
 +import ra
 +import core
 +import constants
  
  from errors import convert_svn_error, NoSvnRepositoryPresent
+ import urlparse
+ import urllib
  
 -svn_config = svn.core.svn_config_get_config(None)
 +svn_config = core.get_config(None)
  
  def get_client_string():
      """Return a string that can be send as part of the User Agent string."""
      return "bzr%s+bzr-svn%s" % (bzrlib.__version__, bzrlib.plugins.svn.__version__)
  
 - 
 -def create_svn_client(url):
 -    from auth import create_auth_baton
 -    client = svn.client.create_context()
 -    client.auth_baton = create_auth_baton(url)
 -    client.config = svn_config
 -    return client
 -
--
  # Don't run any tests on SvnTransport as it is not intended to be 
  # a full implementation of Transport
  def get_test_permutations():
@@@ -60,225 -75,301 +67,179 @@@ def bzr_to_svn_url(url)
          url.startswith("svn+https://")):
          url = url[len("svn+"):] # Skip svn+
  
+     if url.startswith("http"):
+         # Without this, URLs with + in them break
+         url = _url_unescape_uri(url)
      # The SVN libraries don't like trailing slashes...
-     return url.rstrip('/')
+     url = url.rstrip('/')
  
+     return url
  
- class SvnRaTransport(Transport):
-     """Fake transport for Subversion-related namespaces.
-     
-     This implements just as much of Transport as is necessary 
-     to fool Bazaar. """
-     @convert_svn_error
-     def __init__(self, url="", _backing_url=None):
-         bzr_url = url
-         self.svn_url = bzr_to_svn_url(url)
-         self._root = None
-         # _backing_url is an evil hack so the root directory of a repository 
-         # can be accessed on some HTTP repositories. 
-         if _backing_url is None:
-             _backing_url = self.svn_url
-         self._backing_url = _backing_url.rstrip("/")
-         Transport.__init__(self, bzr_url)
  
 -class Editor(object):
 -    """Simple object wrapper around the Subversion delta editor interface."""
 -    def __init__(self, connection, (editor, editor_baton)):
 -        self.editor = editor
 -        self.editor_baton = editor_baton
 -        self.recent_baton = []
 -        self._connection = connection
 -
 -    @convert_svn_error
 -    def open_root(self, base_revnum):
 -        assert self.recent_baton == [], "root already opened"
 -        baton = svn.delta.editor_invoke_open_root(self.editor, 
 -                self.editor_baton, base_revnum)
 -        self.recent_baton.append(baton)
 -        return baton
 -
 -    @convert_svn_error
 -    def close_directory(self, baton, *args, **kwargs):
 -        assert self.recent_baton.pop() == baton, \
 -                "only most recently opened baton can be closed"
 -        svn.delta.editor_invoke_close_directory(self.editor, baton, *args, **kwargs)
 -
 -    @convert_svn_error
 -    def close(self):
 -        assert self.recent_baton == []
 -        svn.delta.editor_invoke_close_edit(self.editor, self.editor_baton)
 -        self._connection._unmark_busy()
 -
 -    @convert_svn_error
 -    def apply_textdelta(self, baton, *args, **kwargs):
 -        assert self.recent_baton[-1] == baton
 -        return svn.delta.editor_invoke_apply_textdelta(self.editor, baton,
 -                *args, **kwargs)
 -
 -    @convert_svn_error
 -    def change_dir_prop(self, baton, name, value, pool=None):
 -        assert self.recent_baton[-1] == baton
 -        return svn.delta.editor_invoke_change_dir_prop(self.editor, baton, 
 -                                                       name, value, pool)
 -
 -    @convert_svn_error
 -    def delete_entry(self, *args, **kwargs):
 -        return svn.delta.editor_invoke_delete_entry(self.editor, *args, **kwargs)
 -
 -    @convert_svn_error
 -    def add_file(self, path, parent_baton, *args, **kwargs):
 -        assert self.recent_baton[-1] == parent_baton
 -        baton = svn.delta.editor_invoke_add_file(self.editor, path, 
 -            parent_baton, *args, **kwargs)
 -        self.recent_baton.append(baton)
 -        return baton
 -
 -    @convert_svn_error
 -    def open_file(self, path, parent_baton, *args, **kwargs):
 -        assert self.recent_baton[-1] == parent_baton
 -        baton = svn.delta.editor_invoke_open_file(self.editor, path, 
 -                                                 parent_baton, *args, **kwargs)
 -        self.recent_baton.append(baton)
 -        return baton
 -
 -    @convert_svn_error
 -    def change_file_prop(self, baton, name, value, pool=None):
 -        assert self.recent_baton[-1] == baton
 -        svn.delta.editor_invoke_change_file_prop(self.editor, baton, name, 
 -                                                 value, pool)
 -
 -    @convert_svn_error
 -    def close_file(self, baton, *args, **kwargs):
 -        assert self.recent_baton.pop() == baton
 -        svn.delta.editor_invoke_close_file(self.editor, baton, *args, **kwargs)
 -
 -    @convert_svn_error
 -    def add_directory(self, path, parent_baton, *args, **kwargs):
 -        assert self.recent_baton[-1] == parent_baton
 -        baton = svn.delta.editor_invoke_add_directory(self.editor, path, 
 -            parent_baton, *args, **kwargs)
 -        self.recent_baton.append(baton)
 -        return baton
 -
 -    @convert_svn_error
 -    def open_directory(self, path, parent_baton, *args, **kwargs):
 -        assert self.recent_baton[-1] == parent_baton
 -        baton = svn.delta.editor_invoke_open_directory(self.editor, path, 
 -            parent_baton, *args, **kwargs)
 -        self.recent_baton.append(baton)
 -        return baton
 -
 -
+ def needs_busy(unbound):
+     """Decorator that marks a connection as busy before running a methd on it.
+     """
+     def convert(self, *args, **kwargs):
+         self._mark_busy()
+         try:
+             return unbound(self, *args, **kwargs)
+         finally:
+             self._unmark_busy()
+     convert.__doc__ = unbound.__doc__
+     convert.__name__ = unbound.__name__
+     return convert
 -        self._client = create_svn_client(url)
+ class Connection(object):
+     """An single connection to a Subversion repository. This usually can 
+     only do one operation at a time."""
+     def __init__(self, url):
+         self._busy = False
+         self._root = None
+         self._unbusy_handler = None
++        self.url = url
          try:
-             self.mutter('opening SVN RA connection to %r' % self._backing_url)
-             self._ra = ra.RemoteAccess(self._backing_url.encode('utf8'), 
+             self.mutter('opening SVN RA connection to %r' % url)
 -            self._ra = svn.client.open_ra_session(url.encode('utf8'), 
 -                    self._client)
++            self._ra = ra.RemoteAccess(url.encode('utf8'), 
 +                    auth=create_auth_baton())
 +            # FIXME: Callbacks
          except SubversionException, (_, num):
 -            if num in (svn.core.SVN_ERR_RA_SVN_REPOS_NOT_FOUND,):
 +            if num in (constants.ERR_RA_SVN_REPOS_NOT_FOUND,):
                  raise NoSvnRepositoryPresent(url=url)
 -            if num == svn.core.SVN_ERR_BAD_URL:
 +            if num == constants.ERR_BAD_URL:
                  raise InvalidURL(url)
              raise
 -        self.url = url
  
 -    class Reporter(object):
 -        def __init__(self, connection, (reporter, report_baton)):
 -            self._reporter = reporter
 -            self._baton = report_baton
 -            self._connection = connection
 -
 -        @convert_svn_error
 -        def set_path(self, path, revnum, start_empty, lock_token, pool=None):
 -            svn.ra.reporter2_invoke_set_path(self._reporter, self._baton, 
 -                        path, revnum, start_empty, lock_token, pool)
 -
 -        @convert_svn_error
 -        def delete_path(self, path, pool=None):
 -            svn.ra.reporter2_invoke_delete_path(self._reporter, self._baton,
 -                    path, pool)
 -
 -        @convert_svn_error
 -        def link_path(self, path, url, revision, start_empty, lock_token, 
 -                      pool=None):
 -            svn.ra.reporter2_invoke_link_path(self._reporter, self._baton,
 -                    path, url, revision, start_empty, lock_token,
 -                    pool)
 -
 -        @convert_svn_error
 -        def finish_report(self, pool=None):
 -            try:
 -                svn.ra.reporter2_invoke_finish_report(self._reporter, 
 -                        self._baton, pool)
 -            finally:
 -                self._connection._unmark_busy()
 -
 -        @convert_svn_error
 -        def abort_report(self, pool=None):
 -            try:
 -                svn.ra.reporter2_invoke_abort_report(self._reporter, 
 -                        self._baton, pool)
 -            finally:
 -                self._connection._unmark_busy()
 +        from bzrlib.plugins.svn import lazy_check_versions
 +        lazy_check_versions()
  
-     def mutter(self, text):
-         if 'transport' in debug.debug_flags:
-             mutter(text)
+     def is_busy(self):
+         return self._busy
  
-     def has(self, relpath):
-         """See Transport.has()."""
-         # TODO: Raise TransportNotPossible here instead and 
-         # catch it in bzrdir.py
-         return False
+     def _mark_busy(self):
+         assert not self._busy, "already busy"
+         self._busy = True
  
-     def get(self, relpath):
-         """See Transport.get()."""
-         # TODO: Raise TransportNotPossible here instead and 
-         # catch it in bzrdir.py
-         raise NoSuchFile(path=relpath)
+     def set_unbusy_handler(self, handler):
+         self._unbusy_handler = handler
  
-     def stat(self, relpath):
-         """See Transport.stat()."""
-         raise TransportNotPossible('stat not supported on Subversion')
+     def _unmark_busy(self):
+         assert self._busy, "not busy"
+         self._busy = False
+         if self._unbusy_handler is not None:
+             self._unbusy_handler()
+             self._unbusy_handler = None
+     def mutter(self, text):
+         if 'transport' in debug.debug_flags:
 -            mutter(text)
++                mutter(text)
  
      @convert_svn_error
 -    @needs_busy
      def get_uuid(self):
          self.mutter('svn get-uuid')
 -        return svn.ra.get_uuid(self._ra)
 +        return self._ra.get_uuid()
  
-     def get_repos_root(self):
-         root = self.get_svn_repos_root()
-         if (self.base.startswith("svn+http:") or 
-             self.base.startswith("svn+https:")):
-             return "svn+%s" % root
-         return root
      @convert_svn_error
-     def get_svn_repos_root(self):
+     @needs_busy
+     def get_repos_root(self):
          if self._root is None:
              self.mutter("svn get-repos-root")
 -            self._root = svn.ra.get_repos_root(self._ra)
 +            self._root = self._ra.get_repos_root()
          return self._root
  
      @convert_svn_error
 -    @needs_busy
      def get_latest_revnum(self):
          self.mutter("svn get-latest-revnum")
 -        return svn.ra.get_latest_revnum(self._ra)
 -
 -    def _make_editor(self, editor, pool=None):
 -        edit, edit_baton = svn.delta.make_editor(editor, pool)
 -        self._edit = edit
 -        self._edit_baton = edit_baton
 -        return self._edit, self._edit_baton
 +        return self._ra.get_latest_revnum()
  
      @convert_svn_error
 -    def do_switch(self, switch_rev, recurse, switch_url, editor, pool=None):
 +    def do_switch(self, switch_rev, recurse, switch_url, editor):
-         self._open_real_transport()
          self.mutter('svn switch -r %d -> %r' % (switch_rev, switch_url))
 -        self._mark_busy()
 -        edit, edit_baton = self._make_editor(editor, pool)
 -        return self.Reporter(self, svn.ra.do_switch(self._ra, switch_rev, "", 
 -                             recurse, switch_url, edit, edit_baton, pool))
 +        return self._ra.do_switch(switch_rev, "", recurse, switch_url, editor)
  
      @convert_svn_error
 -    def change_rev_prop(self, revnum, name, value, pool=None):
 -        self.mutter('svn revprop -r%d --set %s=%s' % (revnum, name, value))
 -        svn.ra.change_rev_prop(self._ra, revnum, name, value)
 +    def get_log(self, path, from_revnum, to_revnum, limit, discover_changed_paths, 
 +                strict_node_history, revprops, rcvr):
 +        self.mutter('svn log %r:%r %r' % (from_revnum, to_revnum, path))
 +        return self._ra.get_log(rcvr, [self._request_path(path)], 
 +                              from_revnum, to_revnum, limit, discover_changed_paths, 
 +                              strict_node_history, revprops)
  
-     def _open_real_transport(self):
-         if self._backing_url != self.svn_url:
-             self.reparent(self.base)
-         assert self._backing_url == self.svn_url
-     def reparent_root(self):
-         if self._is_http_transport():
-             self.svn_url = self.get_svn_repos_root()
-             self.base = self.get_repos_root()
-         else:
-             self.reparent(self.get_repos_root())
      @convert_svn_error
 -    @needs_busy
 -    def get_lock(self, path):
 -        return svn.ra.get_lock(self._ra, path)
 -
 -    @convert_svn_error
 -    @needs_busy
 -    def unlock(self, locks, break_lock=False):
 -        def lock_cb(baton, path, do_lock, lock, ra_err, pool):
 -            pass
 -        return svn.ra.unlock(self._ra, locks, break_lock, lock_cb)
 +    def change_rev_prop(self, revnum, name, value):
 +        self.mutter('svn revprop -r%d --set %s=%s' % (revnum, name, value))
 +        self._ra.change_rev_prop(revnum, name, value)
-     @convert_svn_error
-     def reparent(self, url):
-         url = url.rstrip("/")
-         self.base = url
-         self.svn_url = bzr_to_svn_url(url)
-         if self.svn_url == self._backing_url:
-             return
-         if hasattr(self._ra, 'reparent'):
-             self.mutter('svn reparent %r' % url)
-             self._ra.reparent(self.svn_url)
-         else:
-             self.mutter('svn reparent (reconnect) %r' % url)
-             self._ra = self._client.open_ra_session(self.svn_url.encode('utf8'))
-         self._backing_url = self.svn_url
+  
      @convert_svn_error
-     def get_dir(self, path, revnum, kind=False):
+     @needs_busy
+     def get_dir(self, path, revnum, pool=None, kind=False):
          self.mutter("svn ls -r %d '%r'" % (revnum, path))
          assert len(path) == 0 or path[0] != "/"
-         path = self._request_path(path)
          # ra_dav backends fail with strange errors if the path starts with a 
          # slash while other backends don't.
 -        if hasattr(svn.ra, 'get_dir2'):
 -            fields = 0
 -            if kind:
 -                fields += svn.core.SVN_DIRENT_KIND
 -            return svn.ra.get_dir2(self._ra, path, revnum, fields)
 -        else:
 -            return svn.ra.get_dir(self._ra, path, revnum)
 +        fields = 0
 +        if kind:
 +            fields += core.SVN_DIRENT_KIND
 +        return self._ra.get_dir(path, revnum, fields)
 +
-     def _request_path(self, relpath):
-         if self._backing_url == self.svn_url:
-             return relpath.strip("/")
-         newrelpath = urlutils.join(
-                 urlutils.relative_url(self._backing_url+"/", self.svn_url+"/"),
-                 relpath).strip("/")
-         self.mutter('request path %r -> %r' % (relpath, newrelpath))
-         return newrelpath
-     @convert_svn_error
-     def list_dir(self, relpath):
-         assert len(relpath) == 0 or relpath[0] != "/"
-         if relpath == ".":
-             relpath = ""
-         try:
-             (dirents, _, _) = self.get_dir(self._request_path(relpath),
-                                            self.get_latest_revnum())
-         except SubversionException, (msg, num):
-             if num == constants.ERR_FS_NOT_DIRECTORY:
-                 raise NoSuchFile(relpath)
-             raise
-         return dirents.keys()
 +    @convert_svn_error
 +    def get_lock(self, path):
 +        return self._ra.get_lock(path)
 +
 +    class SvnLock(object):
 +        def __init__(self, transport, tokens):
 +            self._tokens = tokens
 +            self._transport = transport
 +
 +        def unlock(self):
 +            self.transport.unlock(self.locks)
 +
 +    @convert_svn_error
 +    def unlock(self, locks, break_lock=False):
 +        def lock_cb(baton, path, do_lock, lock, ra_err):
 +            pass
 +        return self._ra.unlock(locks, break_lock, lock_cb)
 +
 +    @convert_svn_error
 +    def lock_write(self, path_revs, comment=None, steal_lock=False):
 +        return self.PhonyLock() # FIXME
 +        tokens = {}
 +        def lock_cb(baton, path, do_lock, lock, ra_err):
 +            tokens[path] = lock
 +        self._ra.lock(path_revs, comment, steal_lock, lock_cb)
 +        return SvnLock(self, tokens)
  
      @convert_svn_error
+     @needs_busy
      def check_path(self, path, revnum):
          assert len(path) == 0 or path[0] != "/"
-         path = self._request_path(path)
          self.mutter("svn check_path -r%d %s" % (revnum, path))
 -        return svn.ra.check_path(self._ra, path.encode('utf-8'), revnum)
 +        return self._ra.check_path(path.encode('utf-8'), revnum)
  
      @convert_svn_error
 -    @needs_busy
      def mkdir(self, relpath, mode=None):
          assert len(relpath) == 0 or relpath[0] != "/"
-         path = urlutils.join(self.svn_url, relpath)
+         path = urlutils.join(self.url, relpath)
          try:
 -            svn.client.mkdir([path.encode("utf-8")], self._client)
 +            self._client.mkdir([path.encode("utf-8")])
          except SubversionException, (msg, num):
 -            if num == svn.core.SVN_ERR_FS_NOT_FOUND:
 +            if num == constants.ERR_FS_NOT_FOUND:
                  raise NoSuchFile(path)
 -            if num == svn.core.SVN_ERR_FS_ALREADY_EXISTS:
 +            if num == constants.ERR_FS_ALREADY_EXISTS:
                  raise FileExists(path)
              raise
  
      @convert_svn_error
-     def replay(self, revision, low_water_mark, editor, send_deltas=True):
-         self._open_real_transport()
 -    def replay(self, revision, low_water_mark, send_deltas, editor, pool=None):
++    def replay(self, revision, low_water_mark, send_deltas, editor):
          self.mutter('svn replay -r%r:%r' % (low_water_mark, revision))
 -        self._mark_busy()
 -        edit, edit_baton = self._make_editor(editor, pool)
 -        svn.ra.replay(self._ra, revision, low_water_mark, send_deltas,
 -                      edit, edit_baton, pool)
 +        self._ra.replay(revision, low_water_mark, editor, send_deltas)
  
      @convert_svn_error
 -    def do_update(self, revnum, recurse, editor, pool=None):
 +    def do_update(self, revnum, recurse, editor):
-         self._open_real_transport()
          self.mutter('svn update -r %r' % revnum)
 -        self._mark_busy()
 -        edit, edit_baton = self._make_editor(editor, pool)
 -        return self.Reporter(self, svn.ra.do_update(self._ra, revnum, "", 
 -                             recurse, edit, edit_baton, pool))
 +        return self._ra.do_update(revnum, "", recurse, editor)
  
      @convert_svn_error
      def has_capability(self, cap):
  
      @convert_svn_error
      def get_commit_editor(self, revprops, done_cb, lock_token, keep_locks):
 -        self._mark_busy()
 -        try:
 -            if hasattr(svn.ra, 'get_commit_editor3'):
 -                editor = svn.ra.get_commit_editor3(self._ra, revprops, done_cb, 
 -                                                  lock_token, keep_locks)
 -            elif revprops.keys() != [svn.core.SVN_PROP_REVISION_LOG]:
 -                raise NotImplementedError()
 -            else:
 -                editor = svn.ra.get_commit_editor2(self._ra, 
 -                            revprops[svn.core.SVN_PROP_REVISION_LOG],
 -                            done_cb, lock_token, keep_locks)
 -
 -            return Editor(self, editor)
 -        except:
 -            self._unmark_busy()
 -            raise
 +        self._open_real_transport()
 +        return self._ra.get_commit_editor(revprops, done_cb, lock_token, 
 +                                          keep_locks)
  
+     class SvnLock(object):
+         def __init__(self, connection, tokens):
+             self._tokens = tokens
+             self._connection = connection
+         def unlock(self):
+             self._connection.unlock(self.locks)
+     @convert_svn_error
+     @needs_busy
+     def lock_write(self, path_revs, comment=None, steal_lock=False):
+         tokens = {}
+         def lock_cb(baton, path, do_lock, lock, ra_err, pool):
+             tokens[path] = lock
+         svn.ra.lock(self._ra, path_revs, comment, steal_lock, lock_cb)
+         return SvnLock(self, tokens)
+     @convert_svn_error
+     @needs_busy
+     def get_log(self, paths, from_revnum, to_revnum, limit, 
+                 discover_changed_paths, strict_node_history, revprops, rcvr, 
+                 pool=None):
+         if paths is None:
+             paths = ["/"]
+         self.mutter('svn log %r:%r %r (limit: %r)' % (from_revnum, to_revnum, paths, limit))
+         if hasattr(svn.ra, 'get_log2'):
+             return svn.ra.get_log2(self._ra, paths, 
+                            from_revnum, to_revnum, limit, 
+                            discover_changed_paths, strict_node_history, False, 
+                            revprops, rcvr, pool)
+         class LogEntry(object):
+             def __init__(self, changed_paths, rev, author, date, message):
+                 self.changed_paths = changed_paths
+                 self.revprops = {}
+                 if svn.core.SVN_PROP_REVISION_AUTHOR in revprops:
+                     self.revprops[svn.core.SVN_PROP_REVISION_AUTHOR] = author
+                 if svn.core.SVN_PROP_REVISION_LOG in revprops:
+                     self.revprops[svn.core.SVN_PROP_REVISION_LOG] = message
+                 if svn.core.SVN_PROP_REVISION_DATE in revprops:
+                     self.revprops[svn.core.SVN_PROP_REVISION_DATE] = date
+                 # FIXME: Check other revprops
+                 # FIXME: Handle revprops is None
+                 self.revision = rev
+                 self.has_children = None
+         def rcvr_convert(orig_paths, rev, author, date, message, pool):
+             rcvr(LogEntry(orig_paths, rev, author, date, message), pool)
+         return svn.ra.get_log(self._ra, paths, 
+                               from_revnum, to_revnum, limit, discover_changed_paths, 
+                               strict_node_history, rcvr_convert, pool)
+     @convert_svn_error
+     @needs_busy
+     def reparent(self, url):
+         if self.url == url:
+             return
+         if hasattr(svn.ra, 'reparent'):
+             self.mutter('svn reparent %r' % url)
+             svn.ra.reparent(self._ra, url)
+             self.url = url
+         else:
+             raise NotImplementedError(self.reparent)
+ class ConnectionPool(object):
+     """Collection of connections to a Subversion repository."""
+     def __init__(self):
+         self.connections = set()
+     def get(self, url):
+         # Check if there is an existing connection we can use
+         for c in self.connections:
+             assert not c.is_busy(), "busy connection in pool"
+             if c.url == url:
+                 self.connections.remove(c)
+                 return c
+         # Nothing available? Just pick an existing one and reparent:
+         if len(self.connections) == 0:
+             return Connection(url)
+         c = self.connections.pop()
+         try:
+             c.reparent(url)
+             return c
+         except NotImplementedError:
+             self.connections.add(c)
+             return Connection(url)
+         except:
+             self.connections.add(c)
+             raise
+     def add(self, connection):
+         assert not connection.is_busy(), "adding busy connection in pool"
+         self.connections.add(connection)
+     
+ class SvnRaTransport(Transport):
+     """Fake transport for Subversion-related namespaces.
+     
+     This implements just as much of Transport as is necessary 
+     to fool Bazaar. """
+     @convert_svn_error
+     def __init__(self, url="", _backing_url=None, pool=None):
+         self.pool = Pool()
+         bzr_url = url
+         self.svn_url = bzr_to_svn_url(url)
+         # _backing_url is an evil hack so the root directory of a repository 
+         # can be accessed on some HTTP repositories. 
+         if _backing_url is None:
+             _backing_url = self.svn_url
+         self._backing_url = _backing_url.rstrip("/")
+         Transport.__init__(self, bzr_url)
+         if pool is None:
+             self.connections = ConnectionPool()
+             # Make sure that the URL is valid by connecting to it.
+             self.connections.add(self.connections.get(self._backing_url))
+         else:
+             self.connections = pool
+         from bzrlib.plugins.svn import lazy_check_versions
+         lazy_check_versions()
+     def get_connection(self):
+         return self.connections.get(self._backing_url)
+     def add_connection(self, conn):
+         self.connections.add(conn)
+     def has(self, relpath):
+         """See Transport.has()."""
+         # TODO: Raise TransportNotPossible here instead and 
+         # catch it in bzrdir.py
+         return False
+     def get(self, relpath):
+         """See Transport.get()."""
+         # TODO: Raise TransportNotPossible here instead and 
+         # catch it in bzrdir.py
+         raise NoSuchFile(path=relpath)
+     def stat(self, relpath):
+         """See Transport.stat()."""
+         raise TransportNotPossible('stat not supported on Subversion')
+     def get_uuid(self):
+         conn = self.get_connection()
+         try:
+             return conn.get_uuid()
+         finally:
+             self.add_connection(conn)
+     def get_repos_root(self):
+         root = self.get_svn_repos_root()
+         if (self.base.startswith("svn+http:") or 
+             self.base.startswith("svn+https:")):
+             return "svn+%s" % root
+         return root
+     def get_svn_repos_root(self):
+         conn = self.get_connection()
+         try:
+             return conn.get_repos_root()
+         finally:
+             self.add_connection(conn)
+     def get_latest_revnum(self):
+         conn = self.get_connection()
+         try:
+             return conn.get_latest_revnum()
+         finally:
+             self.add_connection(conn)
+     def do_switch(self, switch_rev, recurse, switch_url, editor, pool=None):
+         conn = self._open_real_transport()
+         conn.set_unbusy_handler(lambda: self.add_connection(conn))
+         return conn.do_switch(switch_rev, recurse, switch_url, editor, pool)
+     def iter_log(self, paths, from_revnum, to_revnum, limit, discover_changed_paths, 
+                  strict_node_history, revprops):
+         assert paths is None or isinstance(paths, list)
+         assert paths is None or all([isinstance(x, str) for x in paths])
+         assert isinstance(from_revnum, int) and isinstance(to_revnum, int)
+         assert isinstance(limit, int)
+         from threading import Thread, Semaphore
+         class logfetcher(Thread):
+             def __init__(self, transport, **kwargs):
+                 Thread.__init__(self)
+                 self.setDaemon(True)
+                 self.transport = transport
+                 self.kwargs = kwargs
+                 self.pending = []
+                 self.conn = None
+                 self.semaphore = Semaphore(0)
+             def next(self):
+                 self.semaphore.acquire()
+                 ret = self.pending.pop(0)
+                 if ret is None:
+                     self.transport.add_connection(self.conn)
+                 elif isinstance(ret, Exception):
+                     self.transport.add_connection(self.conn)
+                     raise ret
+                 return ret
+             def run(self):
+                 assert self.conn is None, "already running"
+                 def rcvr(log_entry, pool):
+                     self.pending.append((log_entry.changed_paths, log_entry.revision, log_entry.revprops))
+                     self.semaphore.release()
+                 self.conn = self.transport.get_connection()
+                 try:
+                     self.conn.get_log(rcvr=rcvr, **self.kwargs)
+                     self.pending.append(None)
+                 except Exception, e:
+                     self.pending.append(e)
+                 self.semaphore.release()
+         if paths is None:
+             newpaths = None
+         else:
+             newpaths = [self._request_path(path) for path in paths]
+         
+         fetcher = logfetcher(self, paths=newpaths, from_revnum=from_revnum, to_revnum=to_revnum, limit=limit, discover_changed_paths=discover_changed_paths, strict_node_history=strict_node_history, revprops=revprops)
+         fetcher.start()
+         return iter(fetcher.next, None)
+     def get_log(self, paths, from_revnum, to_revnum, limit, discover_changed_paths, 
+                 strict_node_history, revprops, rcvr, pool=None):
+         assert paths is None or isinstance(paths, list), "Invalid paths"
+         assert paths is None or all([isinstance(x, str) for x in paths])
+         if paths is None:
+             newpaths = None
+         else:
+             newpaths = [self._request_path(path) for path in paths]
+         conn = self.get_connection()
+         try:
+             return conn.get_log(newpaths, 
+                     from_revnum, to_revnum,
+                     limit, discover_changed_paths, strict_node_history, 
+                     revprops, rcvr, pool)
+         finally:
+             self.add_connection(conn)
+     def _open_real_transport(self):
+         if self._backing_url != self.svn_url:
+             return self.connections.get(self.svn_url)
+         return self.get_connection()
+     def change_rev_prop(self, revnum, name, value, pool=None):
+         conn = self.get_connection()
+         try:
+             return conn.change_rev_prop(revnum, name, value, pool)
+         finally:
+             self.add_connection(conn)
+     def get_dir(self, path, revnum, pool=None, kind=False):
+         path = self._request_path(path)
+         conn = self.get_connection()
+         try:
+             return conn.get_dir(path, revnum, pool, kind)
+         finally:
+             self.add_connection(conn)
+     def mutter(self, text):
+         if 'transport' in debug.debug_flags:
+             mutter(text)
+     def _request_path(self, relpath):
+         if self._backing_url == self.svn_url:
+             return relpath.strip("/")
+         newsvnurl = urlutils.join(self.svn_url, relpath)
+         if newsvnurl == self._backing_url:
+             return ""
+         newrelpath = urlutils.relative_url(self._backing_url+"/", newsvnurl+"/").strip("/")
+         self.mutter('request path %r -> %r' % (relpath, newrelpath))
+         return newrelpath
+     def list_dir(self, relpath):
+         assert len(relpath) == 0 or relpath[0] != "/"
+         if relpath == ".":
+             relpath = ""
+         try:
+             (dirents, _, _) = self.get_dir(relpath, self.get_latest_revnum())
+         except SubversionException, (msg, num):
+             if num == svn.core.SVN_ERR_FS_NOT_DIRECTORY:
+                 raise NoSuchFile(relpath)
+             raise
+         return dirents.keys()
+     def check_path(self, path, revnum):
+         path = self._request_path(path)
+         conn = self.get_connection()
+         try:
+             return conn.check_path(path, revnum)
+         finally:
+             self.add_connection(conn)
+     def mkdir(self, relpath, mode=None):
+         conn = self.get_connection()
+         try:
+             return conn.mkdir(relpath, mode)
+         finally:
+             self.add_connection(conn)
+     def replay(self, revision, low_water_mark, send_deltas, editor, pool=None):
+         conn = self._open_real_transport()
+         try:
+             return conn.replay(revision, low_water_mark, 
+                                              send_deltas, editor, pool)
+         finally:
+             self.add_connection(conn)
+     def do_update(self, revnum, recurse, editor, pool=None):
+         conn = self._open_real_transport()
+         conn.set_unbusy_handler(lambda: self.add_connection(conn))
+         return conn.do_update(revnum, recurse, editor, pool)
+     def has_capability(self, cap):
+         conn = self.get_connection()
+         try:
+             return conn.has_capability(cap)
+         finally:
+             self.add_connection(conn)
+     def revprop_list(self, revnum, pool=None):
+         conn = self.get_connection()
+         try:
+             return conn.revprop_list(revnum, pool)
+         finally:
+             self.add_connection(conn)
+     def get_commit_editor(self, revprops, done_cb, lock_token, keep_locks):
+         conn = self._open_real_transport()
+         conn.set_unbusy_handler(lambda: self.add_connection(conn))
+         return conn.get_commit_editor(revprops, done_cb,
+                                      lock_token, keep_locks)
      def listable(self):
          """See Transport.listable().
          """
diff --cc tree.py
index 1a190de8707cd701969edc63ef9888bd31db493f,31f58489b39ca6edb1a4fa623d6cdae8cf76e7fd..95d87a3f8f7f415dce3222052d97ba2cd0d827ce
+++ b/tree.py
@@@ -1,4 -1,4 +1,4 @@@
--# Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
++# 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
@@@ -152,86 -172,60 +152,72 @@@ class DirectoryTreeEditor
          file_id, revision_id = self.tree.id_map[path]
          ie = self.tree._inventory.add_path(path, 'directory', file_id)
          ie.revision = revision_id
 -        return file_id
 -
 -    def change_dir_prop(self, id, name, value, pool):
 -        if name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
 -            self.dir_revnum[id] = int(value)
 -        elif name == svn.core.SVN_PROP_IGNORE:
 -            self.dir_ignores[id] = value
 -        elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
 -                      svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
 -                      svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
 -                      svn.core.SVN_PROP_ENTRY_UUID,
 -                      svn.core.SVN_PROP_EXECUTABLE):
 +        return DirectoryTreeEditor(self.tree, file_id)
 +
++<<<<<<< TREE
 +    def change_prop(self, name, value):
 +        from mapping import (SVN_PROP_BZR_ANCESTRY, 
 +                        SVN_PROP_BZR_PREFIX, SVN_PROP_BZR_REVISION_INFO, 
 +                        SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_REVISION_ID,
 +                        SVN_PROP_BZR_BRANCHING_SCHEME, SVN_PROP_BZR_MERGE)
 +
-         if name.startswith(SVN_PROP_BZR_ANCESTRY):
-             if self.file_id != self.tree._inventory.root.file_id:
-                 mutter('%r set on non-root dir!' % name)
-                 return
-         elif name in (SVN_PROP_BZR_FILEIDS, SVN_PROP_BZR_BRANCHING_SCHEME):
-             if self.file_id != self.tree._inventory.root.file_id:
-                 mutter('%r set on non-root dir!' % name)
-                 return
-         elif name in (constants.PROP_ENTRY_COMMITTED_DATE,
++        if name in (constants.PROP_ENTRY_COMMITTED_DATE,
 +                      constants.PROP_ENTRY_COMMITTED_REV,
 +                      constants.PROP_ENTRY_LAST_AUTHOR,
 +                      constants.PROP_ENTRY_LOCK_TOKEN,
 +                      constants.PROP_ENTRY_UUID,
 +                      constants.PROP_EXECUTABLE,
 +                      constants.PROP_IGNORE):
              pass
 -        elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
 +        elif name.startswith(constants.PROP_WC_PREFIX):
              pass
-         elif (name == SVN_PROP_BZR_REVISION_INFO or 
-               name.startswith(SVN_PROP_BZR_REVISION_ID)):
-             pass
-         elif name == SVN_PROP_BZR_MERGE:
-             pass
-         elif (name.startswith(constants.PROP_PREFIX) or
-               name.startswith(SVN_PROP_BZR_PREFIX)):
 -        elif name.startswith(svn.core.SVN_PROP_PREFIX):
++        elif name.startswith(constants.PROP_PREFIX):
              mutter('unsupported dir property %r' % name)
  
 -    def change_file_prop(self, id, name, value, pool):
 -        if name == svn.core.SVN_PROP_EXECUTABLE:
 +    def add_file(self, path, copyfrom_path=None, copyfrom_revnum=-1):
 +        path = path.decode("utf-8")
 +        return FileTreeEditor(self.tree, path)
 +
 +    def close(self):
 +        pass
 +
 +
 +class FileTreeEditor:
 +    def __init__(self, tree, path):
 +        self.tree = tree
 +        self.path = path
 +        self.is_executable = False
 +        self.is_symlink = False
 +        self.last_file_rev = None
 +
 +    def change_prop(self, name, value):
 +        from mapping import SVN_PROP_BZR_PREFIX
 +
 +        if name == constants.PROP_EXECUTABLE:
              self.is_executable = (value != None)
 -        elif name == svn.core.SVN_PROP_SPECIAL:
 +        elif name == constants.PROP_SPECIAL:
              self.is_symlink = (value != None)
 -        elif name == svn.core.SVN_PROP_EXTERNALS:
 +        elif name == constants.PROP_EXTERNALS:
              mutter('%r property on file!' % name)
 -        elif name == svn.core.SVN_PROP_ENTRY_COMMITTED_REV:
 +        elif name == constants.PROP_ENTRY_COMMITTED_REV:
              self.last_file_rev = int(value)
 -        elif name in (svn.core.SVN_PROP_ENTRY_COMMITTED_DATE,
 -                      svn.core.SVN_PROP_ENTRY_LAST_AUTHOR,
 -                      svn.core.SVN_PROP_ENTRY_LOCK_TOKEN,
 -                      svn.core.SVN_PROP_ENTRY_UUID,
 -                      svn.core.SVN_PROP_MIME_TYPE):
 +        elif name in (constants.PROP_ENTRY_COMMITTED_DATE,
 +                      constants.PROP_ENTRY_LAST_AUTHOR,
 +                      constants.PROP_ENTRY_LOCK_TOKEN,
 +                      constants.PROP_ENTRY_UUID,
 +                      constants.PROP_MIME_TYPE):
              pass
 -        elif name.startswith(svn.core.SVN_PROP_WC_PREFIX):
 +        elif name.startswith(constants.PROP_WC_PREFIX):
              pass
-         elif (name.startswith(constants.PROP_PREFIX) or
-               name.startswith(SVN_PROP_BZR_PREFIX)):
 -        elif name.startswith(svn.core.SVN_PROP_PREFIX):
++        elif name.startswith(constants.SVN_PROP_PREFIX):
              mutter('unsupported file property %r' % name)
  
 -    def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
 -        path = path.decode("utf-8")
 -        self.is_symlink = False
 -        self.is_executable = False
 -        return path
 -
 -    def close_dir(self, id):
 -        if id in self.tree._inventory and self.dir_ignores.has_key(id):
 -            self.tree._inventory[id].ignores = self.dir_ignores[id]
 -
 -    def close_file(self, path, checksum):
 -        file_id, revision_id = self.tree.id_map[path]
 +    def close(self, checksum=None):
 +        file_id, revision_id = self.tree.id_map[self.path]
          if self.is_symlink:
 -            ie = self.tree._inventory.add_path(path, 'symlink', file_id)
 +            ie = self.tree._inventory.add_path(self.path, 'symlink', file_id)
          else:
 -            ie = self.tree._inventory.add_path(path, 'file', file_id)
 +            ie = self.tree._inventory.add_path(self.path, 'file', file_id)
          ie.revision = revision_id
  
          if self.file_stream:
@@@ -296,14 -298,14 +282,14 @@@ class SvnBasisTree(RevisionTree)
  
          def find_ids(entry):
              relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
 -            if entry.schedule in (svn.wc.schedule_normal
 -                                  svn.wc.schedule_delete
 -                                  svn.wc.schedule_replace):
 +            if entry.schedule in (wc.SCHEDULE_NORMAL
 +                                  wc.SCHEDULE_DELETE
 +                                  wc.SCHEDULE_REPLACE):
-                 return self.id_map[workingtree.branch.unprefix(relpath)]
+                 return self.id_map[workingtree.branch.unprefix(relpath.decode("utf-8"))]
              return (None, None)
  
 -        def add_dir_to_inv(relpath, wc, parent_id):
 -            entries = svn.wc.entries_read(wc, False)
 +        def add_dir_to_inv(relpath, adm, parent_id):
 +            entries = adm.entries_read(False)
              entry = entries[""]
              (id, revid) = find_ids(entry)
              if id == None:
              # First handle directory itself
              ie = self._inventory.add_path(relpath, 'directory', id)
              ie.revision = revid
-             if relpath == "":
+             if relpath == u"":
                  self._inventory.revision_id = revid
  
-             for name in entries:
-                 if name == "":
+             for name, entry in entries.items():
+                 name = name.decode("utf-8")
+                 if name == u"":
                      continue
  
+                 assert isinstance(relpath, unicode)
+                 assert isinstance(name, unicode)
                  subrelpath = os.path.join(relpath, name)
  
-                 entry = entries[name]
                  assert entry
                  
 -                if entry.kind == svn.core.svn_node_dir:
 -                    subwc = svn.wc.adm_open3(wc
 +                if entry.kind == core.NODE_DIR:
 +                    subwc = wc.WorkingCopy(adm
                              self.workingtree.abspath(subrelpath), 
                                               False, 0, None)
                      try:
                  else:
                      (subid, subrevid) = find_ids(entry)
                      if subid is not None:
 -                        add_file_to_inv(subrelpath, subid, subrevid, wc)
 +                        add_file_to_inv(subrelpath, subid, subrevid, adm)
  
 -        wc = workingtree._get_wc() 
 +        adm = workingtree._get_wc() 
          try:
-             add_dir_to_inv("", adm, None)
 -            add_dir_to_inv(u"", wc, None)
++            add_dir_to_inv(u"", adm, None)
          finally:
 -            svn.wc.adm_close(wc)
 +            adm.close()
  
      def _abspath(self, relpath):
-         return wc.get_pristine_copy_path(self.workingtree.abspath(relpath))
 -        return svn.wc.get_pristine_copy_path(self.workingtree.abspath(relpath).encode("utf-8"))
++        return wc.get_pristine_copy_path(self.workingtree.abspath(relpath).encode("utf-8"))
  
      def get_file_lines(self, file_id):
          base_copy = self._abspath(self.id2path(file_id))
diff --cc workingtree.py
index 41880730bc769cf6e446aa4dc05844caaba2470a,0559d1156215765ab4515c1617796b6f65ba6758..897801c1a3277f23a5977d3af20aa75ddadb789f
@@@ -34,10 -34,9 +34,10 @@@ from bzrlib.workingtree import WorkingT
  
  from branch import SvnBranch
  from commit import _revision_id_to_svk_feature
 +import constants
  from convert import SvnConverter
  from errors import LocalCommitsUnsupported, NoSvnRepositoryPresent
- from mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_FILEIDS, 
+ from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_FILEIDS, 
                       SVN_PROP_BZR_REVISION_ID, SVN_PROP_BZR_REVISION_INFO,
                       generate_revision_metadata)
  from remote import SvnRemoteAccess
@@@ -73,25 -73,25 +73,21 @@@ def generate_ignore_list(ignore_map)
  class SvnWorkingTree(WorkingTree):
      """WorkingTree implementation that uses a Subversion Working Copy for storage."""
      def __init__(self, bzrdir, local_path, branch):
 -        self._format = SvnWorkingTreeFormat()
 +        version = wc.check_wc(local_path)
 +        self._format = SvnWorkingTreeFormat(version)
          self.basedir = local_path
+         assert isinstance(self.basedir, unicode)
          self.bzrdir = bzrdir
          self._branch = branch
 -        self.base_revnum = 0
 -        self.client_ctx = create_svn_client(bzrdir.svn_url)
 -        self.client_ctx.log_msg_func2 = \
 -                svn.client.svn_swig_py_get_commit_log_func
 -
          self._get_wc()
-         (min_rev, max_rev, switched, modified) = \
-                 wc.revision_status(self.basedir, None, True)
+         status = svn.wc.revision_status(self.basedir, None, True, None, None)
+         self.base_revnum = status.max_rev
+         self.base_tree = SvnBasisTree(self)
+         self.base_revid = branch.generate_revision_id(self.base_revnum)
  
-         self.base_tree = None
-         self.base_revnum = max_rev
-         if max_rev < 0:
-             self.base_revid = None
-             self._set_inventory(Inventory(), dirty=False)
-         else:
-             self.base_revid = branch.generate_revision_id(self.base_revnum)
-             self.read_working_inventory()
+         self.read_working_inventory()
  
-         self.controldir = os.path.join(self.basedir, wc.get_adm_dir(), 
+         self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(), 
                                         'bzr')
          try:
              os.makedirs(self.controldir)
@@@ -749,16 -724,13 +745,16 @@@ class SvnCheckout(BzrDir)
          self.local_path = transport.local_abspath(".")
          
          # Open related remote repository + branch
 -        wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
          try:
 -            self.svn_url = svn.wc.entry(self.local_path, wc, True).url
 +            adm = wc.WorkingCopy(None, self.local_path, False, 0, None)
 +        except SubversionException, (msg, constants.ERR_WC_UNSUPPORTED_FORMAT):
 +            raise UnsupportedFormatError(msg, kind='workingtree')
 +        try:
-             svn_url = adm.entry(self.local_path, True).url
++            self.svn_url = adm.entry(self.local_path, True).url
          finally:
 -            svn.wc.adm_close(wc)
 +            adm.close()
  
-         self.remote_transport = SvnRaTransport(svn_url)
+         self.remote_transport = SvnRaTransport(self.svn_url)
          self.remote_bzrdir = SvnRemoteAccess(self.remote_transport)
          self.svn_root_transport = self.remote_transport.clone_root()
          self.root_transport = self.transport = transport