Merge 0.4.
authorJelmer Vernooij <jelmer@samba.org>
Sun, 15 Jun 2008 00:41:08 +0000 (02:41 +0200)
committerJelmer Vernooij <jelmer@samba.org>
Sun, 15 Jun 2008 00:41:08 +0000 (02:41 +0200)
51 files changed:
FAQ
Makefile
NEWS
README
__init__.py
auth.py
branch.py
branchprops.py
client.c [new file with mode: 0644]
commit.py
config.py
convert.py
core.c [new file with mode: 0644]
delta.py [new file with mode: 0644]
editor.c [new file with mode: 0644]
editor.h [new file with mode: 0644]
errors.py
fetch.py
format.py
logwalker.py
mapping.py
mapping3/__init__.py
ra.c [new file with mode: 0644]
remote.py
repos.c [new file with mode: 0644]
repository.py
revids.py
setup.py
tests/__init__.py
tests/test_branch.py
tests/test_checkout.py
tests/test_client.py [new file with mode: 0644]
tests/test_commit.py
tests/test_convert.py
tests/test_core.py [new file with mode: 0644]
tests/test_errors.py
tests/test_fetch.py
tests/test_logwalker.py
tests/test_push.py
tests/test_ra.py [new file with mode: 0644]
tests/test_radir.py
tests/test_repos.py
tests/test_repository.py [new file with mode: 0644]
tests/test_wc.py [new file with mode: 0644]
tests/test_workingtree.py
transport.py
tree.py
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]
wc.c [new file with mode: 0644]
workingtree.py

diff --git a/FAQ b/FAQ
index cf4d248eee6a66351067a74e8b4e5745dc0c6db0..08f715588eddeb4115b992c1c37111134a8af803 100644 (file)
--- a/FAQ
+++ b/FAQ
@@ -13,23 +13,8 @@ proposed for inclusion in Subversion 1.4.7.
 One way to work around this problem is to Ctrl+C the branch operation 
 and restart it.
 
-I am unable to access a repository that requires user/password authentication or uses self-signed SSL certificates
-------------------------------------------------------------------------------------------------------------------
-The Python bindings required for password prompting are only present in 
-version 1.5 of Subversion so password prompting is only possible if 
-you have that version installed.
-
-If you have an older version of Subversion installed, bzr-svn can 
-use passwords cached by Subversion or credentials specified in the URL. 
-
-Subversion can be forced to cache the password by accessing the repository 
-using the Subversion command-line client. 
-For example, try running 'svn info <url>'. 
-
-You can specify the username and password in the URL in the standard way, e.g.::
-
-  $ bzr co svn+http://guest:@tortoisesvn.tigris.org/svn/tortoisesvn/trunk
-
+I am unable to access a repository that uses self-signed SSL certificates
+-------------------------------------------------------------------------
 If you are using a Subversion repository over http or https it may be 
 necessary to prefix the repository URL with "svn+", e.g. 
 svn+http://svn.python.org/projects/python/trunk/
index af25c90c4d91df117c069c7fd3a24a76bb8738b9..742335207c7d999bc216e5dde7a86a5ebff38254 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ PYLINT ?= pylint
 RST2HTML ?= rst2html
 TESTS ?= 
 
-all:: build README.html FAQ.html AUTHORS.html
+all:: build build-inplace README.html FAQ.html AUTHORS.html
 
 build::
        $(SETUP) build
@@ -21,6 +21,7 @@ install::
 
 clean::
        $(SETUP) clean
+       rm -f *.so
 
 TMP_PLUGINS_DIR = $(shell pwd)/.plugins
 
@@ -31,7 +32,7 @@ $(TMP_PLUGINS_DIR)/svn: $(TMP_PLUGINS_DIR)
        ln -sf .. $@
 
 check:: build-inplace $(TMP_PLUGINS_DIR)/svn 
-       BZR_PLUGIN_PATH=$(TMP_PLUGINS_DIR) $(DEBUGGER) $(PYTHON) $(BZR) selftest $(TEST_OPTIONS) --starting-with=bzrlib.plugins.svn $(TESTS)
+       BZR_PLUGIN_PATH=$(TMP_PLUGINS_DIR) $(DEBUGGER) $(PYTHON) $(PYTHON_OPTIONS) $(BZR) selftest $(TEST_OPTIONS) --starting-with=bzrlib.plugins.svn $(TESTS)
 
 check-verbose::
        $(MAKE) check TEST_OPTIONS=-v
@@ -39,6 +40,15 @@ check-verbose::
 check-one::
        $(MAKE) check TEST_OPTIONS=--one
 
+check-random::
+       $(MAKE) check TEST_OPTIONS="--random=now --verbose --one"
+
+valgrind-check:: 
+       $(MAKE) check DEBUGGER="valgrind --suppressions=/usr/lib/valgrind/python.supp $(VALGRIND_OPTIONS)"
+
+gdb-check::
+       $(MAKE) check DEBUGGER="gdb --args $(GDB_OPTIONS)"
+
 show-plugins::
        BZR_PLUGIN_PATH=$(TMP_PLUGINS_DIR) $(BZR) plugins
 
diff --git a/NEWS b/NEWS
index b22541ccdbd8b2c816ec5f20b671ea4d86564043..5e07fbbafbb19713f1f92ea4022271956c6fb450 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,14 @@ bzr-svn 0.4.11 UNRELEASED
 
   CHANGES
 
+   * bzr-svn now comes with its own Python bindings for the Subversion 
+     libraries, removing the need for a unreleased version of Subversion and 
+        improving performance.
+
+        It does however mean the extensions have to be built. This requires 
+        the Subversion development libraries and should be possible by 
+        simply running ``make`` from the plugin directory.
+
    * Now uses absolute imports and no longer adds plugin directory to the system path.
 
   BUG FIXES
@@ -114,6 +122,12 @@ bzr-svn 0.4.10  2008-05-12
 
 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.
@@ -137,6 +151,8 @@ bzr-svn 0.4.9    2008-03-23
 
    * Be a bit quieter with messages about experimental mappings. (#162496)
 
+   * Properly warn when trying to open a working copy with a newer version.
+
    * More correct implementation of Repository.get_ancestry(). 
 
    * Properly use current branching scheme when following branches. (#183361)
diff --git a/README b/README
index bfc68d6a9b6707e0c678a2dcaef7e89ef92728da..166a545b6417569003772675fda9ee9c444987d1 100644 (file)
--- a/README
+++ b/README
@@ -165,17 +165,13 @@ SQLite
 
 If you are using Python 2.4, you will need to have the pysqlite module installed. Python 2.5 and higher have sqlite support built in. 
 
-Python-Subversion >= 1.5
-~~~~~~~~~~~~~~~~~~~~~~~~
-You also need a fairly recent version of the official Python bindings to the 
-Subversion libraries. At the moment, the svn plugin only works with 
-Subversion 1.5. The python-subversion (not python-svn!) package 
-in Ubuntu since Feisty and Debian since Etch also contain the 
-required changes. 
-
-The plugin requires a couple of fixes to the Python bindings for Subversion that are only available in Subversion 1.5 and higher. Subversion 1.5 has not been released yet, but packages with the appropriate patches applied to older versions are available for some platforms.
+Subversion development files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+You need the Subversion libraries, including the development files. This should
+be readily packaged for most platforms. The package name for Debian 
+and Debian-based distributions is ``libsvn-dev``.
 bzr-rebase
 ~~~~~~~~~~
 
@@ -198,7 +194,7 @@ Installation
 
 Simply place this directory in ~/.bazaar/plugins and you should be able 
 to check out branches from Subversion using bzr. Make sure the directory 
-name is 'svn'.
+name is 'svn'. The plugin has to be built as well by running ``make``.
 
 Development
 -----------
index f56efe062df8925f1ef617703b62dbde301de9ce..634e3c4e47cde01f75cffff6f2dec238166daca1 100644 (file)
@@ -31,7 +31,7 @@ from bzrlib.plugins.svn import revspec
 # versions ending in 'exp' mean experimental mappings
 # versions ending in 'dev' mean development version
 # versions ending in 'final' mean release (well tested, etc)
-version_info = (0, 4, 11, 'dev', 0)
+version_info = (0, 4, 11, 'exp', 0)
 
 if version_info[3] == 'final':
     version_string = '%d.%d.%d' % version_info[:3]
@@ -64,28 +64,6 @@ def check_bzrlib_version(desired):
         if not (bzrlib_version[0], bzrlib_version[1]-1) in desired:
             raise BzrError('Version mismatch')
 
-def check_subversion_version():
-    """Check that Subversion is compatible.
-
-    """
-    try:
-        import svn.delta
-    except ImportError:
-        warning('No Python bindings for Subversion installed. See the '
-                'bzr-svn README for details.')
-        raise bzrlib.errors.BzrError("missing python subversion bindings")
-    if (not hasattr(svn.delta, 'svn_delta_invoke_txdelta_window_handler') and 
-        not hasattr(svn.delta, 'tx_invoke_window_handler')):
-        warning('Installed Subversion version does not have updated Python '
-                'bindings. See the bzr-svn README for details.')
-        raise bzrlib.errors.BzrError("incompatible python subversion bindings")
-    import svn.core
-    if (svn.core.SVN_VER_MINOR >= 5 and 
-            27729 <= svn.core.SVN_VER_REVISION < 31470):
-        warning('Installed Subversion has buggy svn.ra.get_log() implementation, please install newer.')
-
-    mutter("bzr-svn: using Subversion %d.%d.%d (%s)", svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR, svn.core.SVN_VER_MICRO, svn.core.__file__)
-
 
 def check_rebase_version(min_version):
     """Check what version of bzr-rebase is installed.
@@ -105,7 +83,11 @@ def check_rebase_version(min_version):
         raise RebaseNotPresent(e)
 
 
-check_subversion_version()
+def check_subversion_version():
+    try:
+        import core
+    except:
+        warning("Unable to load bzr-svn extensions - did you build it?")
 
 register_transport_proto('svn+ssh://', 
     help="Access using the Subversion smart server tunneled over SSH.")
@@ -145,6 +127,7 @@ def lazy_check_versions():
         return
     versions_checked = True
     check_bzrlib_version(COMPATIBLE_BZR_VERSIONS)
+    check_subversion_version()
 
 optimizers_registered = False
 def lazy_register_optimizers():
diff --git a/auth.py b/auth.py
index 067d16798898e780bf0a80a0a53d105ba2679b55..0640efaf84dcb3c9d80442a2e854d13ae5f37efc 100644 (file)
--- a/auth.py
+++ 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 bzrlib.plugins.svn.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
+                )
+from bzrlib.plugins.svn import ra
 import urlparse
 import urllib
 
+AUTH_PARAM_DEFAULT_USERNAME = 'svn:auth:username'
+AUTH_PARAM_DEFAULT_PASSWORD = 'svn:auth:password'
 
 class SubversionAuthenticationConfig(AuthenticationConfig):
     """Simple extended version of AuthenticationConfig that can provide 
@@ -43,20 +45,14 @@ class SubversionAuthenticationConfig(AuthenticationConfig):
         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_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 
@@ -67,14 +63,12 @@ class SubversionAuthenticationConfig(AuthenticationConfig):
         :param may_save: Whether or not the username should be saved.
         :param pool: Allocation pool, is ignored.
         """
-        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, 
             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):
@@ -85,21 +79,19 @@ class SubversionAuthenticationConfig(AuthenticationConfig):
         :param cert_info: Certificate information
         :param may_save: Whether this information may be stored.
         """
-        ssl_server_trust = svn_auth_cred_ssl_server_trust_t()
         credentials = self.get_credentials(self.scheme, host=self.host)
         if (credentials is not None and 
             credentials.has_key("verify_certificates") and 
             credentials["verify_certificates"] == False):
-            ssl_server_trust.accepted_failures = (
-                    svn.core.SVN_AUTH_SSL_NOTYETVALID + 
-                    svn.core.SVN_AUTH_SSL_EXPIRED +
-                    svn.core.SVN_AUTH_SSL_CNMISMATCH +
-                    svn.core.SVN_AUTH_SSL_UNKNOWNCA +
-                    svn.core.SVN_AUTH_SSL_OTHER)
+            accepted_failures = (
+                    AUTH_SSL_NOTYETVALID + 
+                    AUTH_SSL_EXPIRED +
+                    AUTH_SSL_CNMISMATCH +
+                    AUTH_SSL_UNKNOWNCA +
+                    AUTH_SSL_OTHER)
         else:
-            ssl_server_trust.accepted_failures = 0
-        ssl_server_trust.may_save = False
-        return ssl_server_trust
+            accepted_failures = 0
+        return (accepted_failures, False)
 
     def get_svn_username_prompt_provider(self, retries):
         """Return a Subversion auth provider for retrieving the username, as 
@@ -107,7 +99,7 @@ class SubversionAuthenticationConfig(AuthenticationConfig):
         
         :param retries: Number of allowed retries.
         """
-        return svn_auth_get_username_prompt_provider(self.get_svn_username, 
+        return get_username_prompt_provider(self.get_svn_username, 
                                                      retries)
 
     def get_svn_simple_prompt_provider(self, retries):
@@ -116,12 +108,13 @@ class SubversionAuthenticationConfig(AuthenticationConfig):
         
         :param retries: Number of allowed retries.
         """
-        return svn_auth_get_simple_prompt_provider(self.get_svn_simple, retries)
+        return get_simple_prompt_provider(self.get_svn_simple, retries)
 
     def get_svn_ssl_server_trust_prompt_provider(self):
         """Return a Subversion auth provider for checking 
         whether a SSL server is trusted."""
-        return svn_auth_get_ssl_server_trust_prompt_provider(self.get_svn_ssl_server_trust)
+        return get_ssl_server_trust_prompt_provider(
+                    self.get_svn_ssl_server_trust)
 
     def get_svn_auth_providers(self):
         """Return a list of auth providers for this authentication file.
@@ -136,34 +129,31 @@ def get_ssl_client_cert_pw(realm, may_save, pool):
     :param realm: Realm, optional.
     :param may_save: Whether the password can be cached.
     """
-    ssl_cred_pw = svn_auth_cred_ssl_client_cert_pw_t()
-    ssl_cred_pw.password = ui_factory.get_password(
+    password = ui_factory.get_password(
             "Please enter password for client certificate[realm=%s]" % realm)
-    ssl_cred_pw.may_save = False
-    return ssl_cred_pw
+    return (password, False)
 
 
 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 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
 
@@ -181,17 +171,16 @@ def create_auth_baton(url):
     # rather than prompting the user.
     providers = get_stock_svn_providers()
 
-    if svn.core.SVN_VER_MAJOR == 1 and svn.core.SVN_VER_MINOR >= 5:
+    (major, minor, patch, tag) = ra.version()
+    if major == 1 and 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)
+    auth_baton = Auth(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)
+        (user, password) = urllib.splitpasswd(creds)
+        if user is not None:
+            auth_baton.set_parameter(AUTH_PARAM_DEFAULT_USERNAME, user)
+        if password is not None:
+            auth_baton.set_parameter(AUTH_PARAM_DEFAULT_PASSWORD, password)
     return auth_baton
index 4da6fe9b902551d35702d9f3e54e8e54406c9e94..1afbaa3fcb8b0cc385617270ac0024cdd5af6975 100644 (file)
--- a/branch.py
+++ b/branch.py
@@ -24,15 +24,14 @@ from bzrlib.inventory import (Inventory)
 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
-
+from bzrlib.plugins.svn import core
 from bzrlib.plugins.svn.commit import push
 from bzrlib.plugins.svn.config import BranchConfig
+from bzrlib.plugins.svn.core import SubversionException
 from bzrlib.plugins.svn.errors import NotSvnBranchPath, ERR_FS_NO_SUCH_REVISION
 from bzrlib.plugins.svn.format import get_rich_root_format
 from bzrlib.plugins.svn.repository import SvnRepository
-from bzrlib.plugins.svn.transport import bzr_to_svn_url, create_svn_client
+from bzrlib.plugins.svn.transport import bzr_to_svn_url
 
 
 class FakeControlFiles(object):
@@ -78,7 +77,7 @@ class SvnBranch(Branch):
             if revnum is None:
                 raise NotBranchError(self.base)
             if self.repository.transport.check_path(branch_path.strip("/"), 
-                revnum) != svn.core.svn_node_dir:
+                revnum) != core.NODE_DIR:
                 raise NotBranchError(self.base)
         except SubversionException, (_, num):
             if num == ERR_FS_NO_SUCH_REVISION:
@@ -174,23 +173,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()
 
+        os.mkdir(to_location)
         svn_url = bzr_to_svn_url(self.base)
-        client_ctx = create_svn_client(svn_url)
-        svn.client.checkout(svn_url, to_location, rev, 
-                            True, client_ctx)
-
-        return WorkingTree.open(to_location)
+        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):
index f492dc2d1aa676802a053df833f0e3f08af273a6..9752e173bd9793ad959445747e1ac3633d925b5e 100644 (file)
 from bzrlib.errors import NoSuchRevision
 from bzrlib.trace import mutter
 
+from bzrlib.plugins.svn.core import SubversionException
 from bzrlib.plugins.svn.errors import ERR_FS_NO_SUCH_REVISION
 
-from svn.core import SubversionException
-import svn.core
-
 
 class PathPropertyProvider(object):
     def __init__(self, log):
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..725c5c0
--- /dev/null
+++ b/client.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <stdbool.h>
+#include <Python.h>
+#include <apr_general.h>
+#include <svn_opt.h>
+#include <svn_client.h>
+
+#include "util.h"
+
+PyAPI_DATA(PyTypeObject) Client_Type;
+
+static bool to_opt_revision(PyObject *arg, svn_opt_revision_t *ret)
+{
+    if (PyInt_Check(arg)) {
+        ret->kind = svn_opt_revision_number;
+        ret->value.number = PyLong_AsLong(arg);
+        return true;
+    } else if (arg == Py_None) {
+        ret->kind = svn_opt_revision_unspecified;
+        return true;
+    } else if (PyString_Check(arg)) {
+        char *text = PyString_AsString(arg);
+        if (!strcmp(text, "HEAD")) {
+            ret->kind = svn_opt_revision_head;
+            return true;
+        } else if (!strcmp(text, "WORKING")) {
+            ret->kind = svn_opt_revision_working;
+            return true;
+        } else if (!strcmp(text, "BASE")) {
+            ret->kind = svn_opt_revision_base;
+            return true;
+        } 
+    } 
+
+    PyErr_SetString(PyExc_ValueError, "Unable to parse revision");
+    return false;
+}
+
+static PyObject *wrap_py_commit_items(const apr_array_header_t *commit_items)
+{
+       PyObject *ret;
+       int i;
+
+    ret = PyList_New(commit_items->nelts);
+       if (ret == NULL)
+               return NULL;
+
+       assert(commit_items->elt_size == sizeof(svn_client_commit_item_2_t *));
+
+       for (i = 0; i < commit_items->nelts; i++) {
+               svn_client_commit_item2_t *commit_item = 
+                       APR_ARRAY_IDX(commit_items, i, svn_client_commit_item2_t *);
+               PyObject *item, *copyfrom;
+
+               if (commit_item->copyfrom_url != NULL)
+                       copyfrom = Py_BuildValue("(si)", commit_item->copyfrom_url, 
+                                                                        commit_item->copyfrom_rev);
+               else
+                       copyfrom = Py_None;
+
+               item = Py_BuildValue("(szlOi)", 
+                                                        /* commit_item->path */ "foo",
+                                                        commit_item->url, commit_item->revision, 
+                                                        copyfrom,
+                                                        commit_item->state_flags);
+
+               if (PyList_SetItem(ret, i, item) != 0)
+                       return NULL;
+       }
+
+       return ret;
+}
+
+static svn_error_t *py_log_msg_func2(const char **log_msg, const char **tmp_file, const apr_array_header_t *commit_items, void *baton, apr_pool_t *pool)
+{
+    PyObject *py_commit_items, *ret, *py_log_msg, *py_tmp_file;
+    if (baton == Py_None)
+        return NULL;
+
+       py_commit_items = wrap_py_commit_items(commit_items);
+       if (py_commit_items == NULL)
+               return py_svn_error();
+
+    ret = PyObject_CallFunction(baton, "O", py_commit_items);
+       Py_DECREF(py_commit_items);
+       if (ret == NULL)
+               return py_svn_error();
+    if (PyTuple_Check(ret)) {
+        py_log_msg = PyTuple_GetItem(ret, 0);
+        py_tmp_file = PyTuple_GetItem(ret, 1);
+    } else {
+        py_tmp_file = Py_None;
+        py_log_msg = ret;
+    }
+    if (py_log_msg != Py_None) {
+        *log_msg = PyString_AsString(py_log_msg);
+    }
+    if (py_tmp_file != Py_None) {
+        *tmp_file = PyString_AsString(py_tmp_file);
+    }
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static PyObject *py_commit_info_tuple(svn_commit_info_t *ci)
+{
+       if (ci == NULL)
+               Py_RETURN_NONE;
+    if (ci->revision == SVN_INVALID_REVNUM)
+        Py_RETURN_NONE;
+    return Py_BuildValue("(izz)", ci->revision, ci->date, ci->author);
+}
+
+typedef struct {
+    PyObject_HEAD
+    svn_client_ctx_t *client;
+    apr_pool_t *pool;
+    PyObject *callbacks;
+} ClientObject;
+
+static PyObject *client_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    ClientObject *ret;
+       PyObject *config = Py_None;
+    char *kwnames[] = { "config", NULL };
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwnames, &config))
+        return NULL;
+
+    ret = PyObject_New(ClientObject, &Client_Type);
+    if (ret == NULL)
+        return NULL;
+
+    ret->pool = Pool();
+       if (ret->pool == NULL) {
+               PyObject_Del(ret);
+               return NULL;
+       }
+
+    if (!check_error(svn_client_create_context(&ret->client, ret->pool))) {
+               apr_pool_destroy(ret->pool);
+               PyObject_Del(ret);
+        return NULL;
+       }
+
+       if (config != Py_None) {
+               PyErr_SetString(PyExc_NotImplementedError, "custom config not supported yet");
+       }
+
+    return (PyObject *)ret;
+}
+
+static void client_dealloc(PyObject *self)
+{
+    ClientObject *client = (ClientObject *)self;
+       if (client->client->log_msg_func2 != NULL) {
+               Py_DECREF((PyObject *)client->client->log_msg_baton2);
+       }
+    apr_pool_destroy(client->pool);
+       PyObject_Del(self);
+}
+
+static PyObject *client_get_log_msg_func(PyObject *self, void *closure)
+{
+    ClientObject *client = (ClientObject *)self;
+    if (client->client->log_msg_func2 == NULL)
+               Py_RETURN_NONE;
+       return client->client->log_msg_baton2;
+}
+
+static int client_set_log_msg_func(PyObject *self, PyObject *func, void *closure)
+{
+    ClientObject *client = (ClientObject *)self;
+
+       if (client->client->log_msg_baton2 != NULL) {
+               Py_DECREF((PyObject *)client->client->log_msg_baton2);
+       }
+       if (func == Py_None) {
+               client->client->log_msg_func2 = NULL;
+               client->client->log_msg_baton2 = Py_None;
+       } else {
+               client->client->log_msg_func2 = py_log_msg_func2;
+               client->client->log_msg_baton2 = (void *)func;
+       }
+    Py_INCREF(func);
+    return 0;
+}
+
+static PyObject *client_add(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    char *path; 
+    ClientObject *client = (ClientObject *)self;
+    bool recursive=true, force=false, no_ignore=false;
+       apr_pool_t *temp_pool;
+    char *kwnames[] = { "path", "recursive", "force", "no_ignore", NULL };
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|bbb", kwnames, 
+                          &path, &recursive, &force, &no_ignore))
+        return NULL;
+       
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+
+       RUN_SVN_WITH_POOL(temp_pool, 
+                                         svn_client_add3(path, recursive, force, no_ignore, 
+                client->client, temp_pool));
+       apr_pool_destroy(temp_pool);
+    Py_RETURN_NONE;
+}
+
+static PyObject *client_checkout(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    ClientObject *client = (ClientObject *)self;
+    char *kwnames[] = { "url", "path", "peg_rev", "rev", "recurse", "ignore_externals", NULL };
+    svn_revnum_t result_rev;
+    svn_opt_revision_t c_peg_rev, c_rev;
+    char *url, *path; 
+       apr_pool_t *temp_pool;
+    PyObject *peg_rev=Py_None, *rev=Py_None;
+    bool recurse=true, ignore_externals=false;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|OObb", kwnames, &url, &path, &peg_rev, &rev, &recurse, &ignore_externals))
+        return NULL;
+
+    if (!to_opt_revision(peg_rev, &c_peg_rev))
+        return NULL;
+    if (!to_opt_revision(rev, &c_rev))
+        return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_checkout2(&result_rev, url, path, 
+        &c_peg_rev, &c_rev, recurse, 
+        ignore_externals, client->client, temp_pool));
+       apr_pool_destroy(temp_pool);
+    return PyLong_FromLong(result_rev);
+}
+
+static PyObject *client_commit(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    PyObject *targets; 
+    ClientObject *client = (ClientObject *)self;
+    bool recurse=true, keep_locks=true;
+       apr_pool_t *temp_pool;
+    svn_commit_info_t *commit_info = NULL;
+       PyObject *ret;
+       apr_array_header_t *apr_targets;
+    char *kwnames[] = { "targets", "recurse", "keep_locks", NULL };
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|bb", kwnames, &targets, &recurse, &keep_locks))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (!string_list_to_apr_array(temp_pool, targets, &apr_targets)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_commit3(&commit_info, 
+                               apr_targets,
+               recurse, keep_locks, client->client, temp_pool));
+    ret = py_commit_info_tuple(commit_info);
+       apr_pool_destroy(temp_pool);
+
+       return ret;
+}
+
+static PyObject *client_mkdir(PyObject *self, PyObject *args)
+{
+    PyObject *paths;
+    svn_commit_info_t *commit_info = NULL;
+       apr_pool_t *temp_pool;
+       apr_array_header_t *apr_paths;
+    ClientObject *client = (ClientObject *)self;
+       PyObject *ret;
+    if (!PyArg_ParseTuple(args, "O", &paths))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (!string_list_to_apr_array(temp_pool, paths, &apr_paths)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+
+    if (!check_error(svn_client_mkdir2(&commit_info, apr_paths,
+                client->client, temp_pool)))
+        return NULL;
+    ret = py_commit_info_tuple(commit_info);
+       apr_pool_destroy(temp_pool);
+
+       return ret;
+}
+
+static PyObject *client_delete(PyObject *self, PyObject *args)
+{
+    PyObject *paths; 
+    bool force=false;
+       apr_pool_t *temp_pool;
+    svn_commit_info_t *commit_info = NULL;
+       PyObject *ret;
+       apr_array_header_t *apr_paths;
+    ClientObject *client = (ClientObject *)self;
+
+    if (!PyArg_ParseTuple(args, "O|b", &paths, &force))
+        return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    if (!string_list_to_apr_array(temp_pool, paths, &apr_paths)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_delete2(&commit_info, 
+                                                                                                       apr_paths,
+                force, client->client, temp_pool));
+
+    ret = py_commit_info_tuple(commit_info);
+
+       apr_pool_destroy(temp_pool);
+
+       return ret;
+}
+
+static PyObject *client_copy(PyObject *self, PyObject *args)
+{
+    char *src_path, *dst_path;
+    PyObject *src_rev=Py_None;
+    svn_commit_info_t *commit_info = NULL;
+       apr_pool_t *temp_pool;
+    svn_opt_revision_t c_src_rev;
+       PyObject *ret;
+    ClientObject *client = (ClientObject *)self;
+    if (!PyArg_ParseTuple(args, "ss|O", &src_path, &dst_path, &src_rev))
+        return NULL;
+    if (!to_opt_revision(src_rev, &c_src_rev))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_copy3(&commit_info, src_path, 
+                &c_src_rev, dst_path, client->client, temp_pool));
+    ret = py_commit_info_tuple(commit_info);
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+
+static PyObject *client_propset(PyObject *self, PyObject *args)
+{
+    char *propname;
+    svn_string_t c_propval;
+    int recurse = true;
+    int skip_checks = false;
+    ClientObject *client = (ClientObject *)self;
+       apr_pool_t *temp_pool;
+    char *target;
+
+    if (!PyArg_ParseTuple(args, "sz#s|bb", &propname, &c_propval.data, &c_propval.len, &target, &recurse, &skip_checks))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_client_propset2(propname, &c_propval,
+                target, recurse, skip_checks, client->client, temp_pool));
+       apr_pool_destroy(temp_pool);
+    Py_RETURN_NONE;
+}
+    
+static PyObject *client_propget(PyObject *self, PyObject *args)
+{
+    svn_opt_revision_t c_peg_rev;
+    svn_opt_revision_t c_rev;
+    apr_hash_t *hash_props;
+    bool recurse = false;
+    char *propname;
+       apr_pool_t *temp_pool;
+    char *target;
+    PyObject *peg_revision;
+    PyObject *revision;
+    ClientObject *client = (ClientObject *)self;
+       PyObject *ret;
+
+    if (!PyArg_ParseTuple(args, "ssOO|b", &propname, &target, &peg_revision, 
+                          &revision, &recurse))
+        return NULL;
+    if (!to_opt_revision(peg_revision, &c_peg_rev))
+        return NULL;
+    if (!to_opt_revision(revision, &c_rev))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(temp_pool, 
+                                         svn_client_propget2(&hash_props, propname, target,
+                &c_peg_rev, &c_rev, recurse, client->client, temp_pool));
+    ret = prop_hash_to_dict(hash_props);
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+
+static PyObject *client_update(PyObject *self, PyObject *args)
+{
+    bool recurse = true;
+    bool ignore_externals = false;
+       apr_pool_t *temp_pool;
+    PyObject *rev = Py_None, *paths;
+    apr_array_header_t *result_revs, *apr_paths;
+    svn_opt_revision_t c_rev;
+    svn_revnum_t ret_rev;
+    PyObject *ret;
+    int i = 0;
+    ClientObject *client = (ClientObject *)self;
+
+    if (!PyArg_ParseTuple(args, "O|Obb", &paths, &rev, &recurse, &ignore_externals))
+        return NULL;
+
+    if (!to_opt_revision(rev, &c_rev))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (!string_list_to_apr_array(temp_pool, paths, &apr_paths)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_update2(&result_revs, 
+            apr_paths, &c_rev, 
+            recurse, ignore_externals, client->client, temp_pool));
+    ret = PyList_New(result_revs->nelts);
+       if (ret == NULL)
+               return NULL;
+       for (i = 0; i < result_revs->nelts; i++) {
+               ret_rev = APR_ARRAY_IDX(result_revs, i, svn_revnum_t);
+        if (PyList_SetItem(ret, i, PyLong_FromLong(ret_rev)) != 0)
+                       return NULL;
+    }
+       apr_pool_destroy(temp_pool);
+    return ret;
+}
+
+static PyObject *client_revprop_get(PyObject *self, PyObject *args)
+{
+    PyObject *rev = Py_None;
+    char *propname, *propval, *url;
+    svn_revnum_t set_rev;
+    svn_opt_revision_t c_rev;
+    svn_string_t *c_val;
+    ClientObject *client = (ClientObject *)self;
+       apr_pool_t *temp_pool;
+       PyObject *ret;
+    if (!PyArg_ParseTuple(args, "sssO", &propname, &propval, &url, &rev))
+        return NULL;
+    if (!to_opt_revision(rev, &c_rev))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_revprop_get(propname, &c_val, url, 
+                &c_rev, &set_rev, client->client, temp_pool));
+    ret = Py_BuildValue("(z#i)", c_val->data, c_val->len, set_rev);
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+
+static PyObject *client_revprop_set(PyObject *self, PyObject *args)
+{
+    PyObject *rev = Py_None;
+    bool force = false;
+    ClientObject *client = (ClientObject *)self;
+    char *propname, *url;
+    svn_revnum_t set_rev;
+    svn_opt_revision_t c_rev;
+       apr_pool_t *temp_pool;
+    svn_string_t c_val;
+    if (!PyArg_ParseTuple(args, "sz#s|Ob", &propname, &c_val.data, &c_val.len, &url, &rev, &force))
+        return NULL;
+    if (!to_opt_revision(rev, &c_rev))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(temp_pool, svn_client_revprop_set(propname, &c_val, url, 
+                &c_rev, &set_rev, force, client->client, temp_pool));
+       apr_pool_destroy(temp_pool);
+    return PyLong_FromLong(set_rev);
+}
+
+static PyObject *client_log(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    char *kwnames[] = { "targets", "callback", "peg_revision", "start", "end", "limit", "discover_changed_paths", "strict_node_history", NULL };
+    PyObject *targets, *callback, *peg_revision=Py_None, *start=Py_None, 
+             *end=Py_None;
+    ClientObject *client = (ClientObject *)self;
+       apr_pool_t *temp_pool;
+    int limit=0; 
+    bool discover_changed_paths=true, strict_node_history=true;
+    svn_opt_revision_t c_peg_rev, c_start_rev, c_end_rev;
+       apr_array_header_t *apr_paths;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OOOlbb", 
+                                     kwnames, &targets, &callback, 
+                                     &peg_revision, &start, &end, 
+                                     &limit, &discover_changed_paths, 
+                                     &strict_node_history))
+        return NULL;
+
+    if (!to_opt_revision(peg_revision, &c_peg_rev))
+        return NULL;
+    if (!to_opt_revision(start, &c_start_rev))
+        return NULL;
+    if (!to_opt_revision(end, &c_end_rev))
+        return NULL;
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (!string_list_to_apr_array(temp_pool, targets, &apr_paths)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+       RUN_SVN_WITH_POOL(temp_pool, svn_client_log3(apr_paths,
+                &c_peg_rev, &c_start_rev, &c_end_rev, limit, discover_changed_paths, strict_node_history, py_svn_log_wrapper, callback, client->client, temp_pool));
+       apr_pool_destroy(temp_pool);
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef client_methods[] = {
+    { "add", (PyCFunction)client_add, METH_VARARGS|METH_KEYWORDS, NULL },
+    { "checkout", (PyCFunction)client_checkout, METH_VARARGS|METH_KEYWORDS, NULL },
+    { "commit", (PyCFunction)client_commit, METH_VARARGS|METH_KEYWORDS, NULL },
+    { "mkdir", client_mkdir, METH_VARARGS, NULL },
+    { "delete", client_delete, METH_VARARGS, NULL },
+    { "copy", client_copy, METH_VARARGS, NULL },
+    { "propset", client_propset, METH_VARARGS, NULL },
+    { "propget", client_propget, METH_VARARGS, NULL },
+    { "update", client_update, METH_VARARGS, NULL },
+    { "revprop_get", client_revprop_get, METH_VARARGS, NULL },
+    { "revprop_set", client_revprop_set, METH_VARARGS, NULL },
+    { "log", (PyCFunction)client_log, METH_KEYWORDS|METH_VARARGS, NULL },
+    { NULL, }
+};
+
+static PyGetSetDef client_getset[] = {
+       { "log_msg_func", client_get_log_msg_func, client_set_log_msg_func, NULL },
+       { NULL, }
+};
+
+PyTypeObject Client_Type = {
+    PyObject_HEAD_INIT(&PyType_Type) 0,
+    .tp_name = "client.Client",
+    .tp_basicsize = sizeof(ClientObject),
+    .tp_methods = client_methods,
+    .tp_dealloc = client_dealloc,
+    .tp_new = client_new,
+       .tp_getset = client_getset
+};
+
+void initclient(void)
+{
+    PyObject *mod;
+
+    if (PyType_Ready(&Client_Type) < 0)
+        return;
+
+       /* Make sure APR is initialized */
+       apr_initialize();
+
+    mod = Py_InitModule3("client", NULL, "Client methods");
+    if (mod == NULL)
+        return;
+
+    Py_INCREF(&Client_Type);
+    PyModule_AddObject(mod, "Client", (PyObject *)&Client_Type);
+}
index ed32e6cc06c31502ac6600581a5a779d0379be95..6bd36c3ce5ee8d1adfd610303af9009dbff313f6 100644 (file)
--- a/commit.py
+++ b/commit.py
@@ -15,8 +15,8 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 """Committing and pushing to Subversion repositories."""
 
-import svn.delta
-from svn.core import Pool, SubversionException, svn_time_to_cstring
+from core import SubversionException, time_to_cstring
+import core
 
 from bzrlib import debug, osutils, urlutils
 from bzrlib.branch import Branch
@@ -30,11 +30,13 @@ from bzrlib.trace import mutter, warning
 from bzrlib.plugins.svn import properties
 
 from cStringIO import StringIO
+
 from bzrlib.plugins.svn.errors import ChangesRootLHSHistory, MissingPrefix, RevpropChangeFailed, ERR_FS_TXN_OUT_OF_DATE, ERR_REPOS_DISABLED_FEATURE
 from bzrlib.plugins.svn.svk import (generate_svk_feature, serialize_svk_features, 
                  parse_svk_features, SVN_PROP_SVK_MERGE)
 from bzrlib.plugins.svn.logwalker import lazy_dict
 from bzrlib.plugins.svn.mapping import parse_revision_id
+from bzrlib.plugins.svn.ra import txdelta_send_stream
 from bzrlib.plugins.svn.repository import SvnRepositoryFormat, SvnRepository
 
 import urllib
@@ -65,7 +67,7 @@ def _check_dirs_exist(transport, bp_parts, base_rev):
     for i in range(len(bp_parts), 0, -1):
         current = bp_parts[:i]
         path = "/".join(current).strip("/")
-        if transport.check_path(path, base_rev) == svn.core.svn_node_dir:
+        if transport.check_path(path, base_rev) == core.NODE_DIR:
             return current
     return []
 
@@ -107,7 +109,6 @@ class SvnCommitBuilder(RootCommitBuilder):
         super(SvnCommitBuilder, self).__init__(repository, parents, 
             config, timestamp, timezone, committer, revprops, revision_id)
         self.branch = branch
-        self.pool = Pool()
 
         # Gather information about revision on top of which the commit is 
         # happening
@@ -187,28 +188,28 @@ class SvnCommitBuilder(RootCommitBuilder):
         """See CommitBuilder.modified_directory()."""
         self.modified_dirs.add(file_id)
 
-    def _file_process(self, file_id, contents, baton):
+    def _file_process(self, file_id, contents, file_editor):
         """Pass the changes to a file to the Subversion commit editor.
 
         :param file_id: Id of the file to modify.
         :param contents: Contents of the file.
-        :param baton: Baton under which the file is known to the editor.
+        :param file_editor: Editor for this file
         """
-        assert baton is not None
-        (txdelta, txbaton) = self.editor.apply_textdelta(baton, None, self.pool)
-        digest = svn.delta.svn_txdelta_send_stream(StringIO(contents), txdelta, txbaton, self.pool)
+        assert file_editor is not None
+        txdelta = file_editor.apply_textdelta(None)
+        digest = txdelta_send_stream(StringIO(contents), txdelta)
         if 'validate' in debug.debug_flags:
             from fetch import md5_strings
             assert digest == md5_strings(contents)
 
-    def _dir_process(self, path, file_id, baton):
+    def _dir_process(self, path, file_id, dir_editor):
         """Pass the changes to a directory to the commit editor.
 
         :param path: Path (from repository root) to the directory.
         :param file_id: File id of the directory
-        :param baton: Baton of the directory for the editor.
+        :param dir_editor: Editor for the directory.
         """
-        assert baton is not None
+        assert dir_editor is not None
         # Loop over entries of file_id in self.old_inv
         # remove if they no longer exist with the same name
         # or parents
@@ -223,10 +224,10 @@ class SvnCommitBuilder(RootCommitBuilder):
                     child_ie.parent_id != self.new_inventory[child_ie.file_id].parent_id or
                     # ... name changed
                     self.new_inventory[child_ie.file_id].name != child_name):
-                    self.mutter('removing %r(%r)', (child_name, child_ie.file_id))
-                    self.editor.delete_entry(
+                    self.mutter('removing %r(%r)', child_name, child_ie.file_id)
+                    dir_editor.delete_entry(
                         urlutils.join(self.branch.get_branch_path(), path, child_name), 
-                        self.base_revnum, baton, self.pool)
+                        self.base_revnum)
 
         # Loop over file children of file_id in self.new_inventory
         for child_name in self.new_inventory[file_id].children:
@@ -242,9 +243,7 @@ class SvnCommitBuilder(RootCommitBuilder):
             # 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_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
             elif (self.old_inv.id2path(child_ie.file_id) != new_child_path or
@@ -252,22 +251,21 @@ class SvnCommitBuilder(RootCommitBuilder):
                 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(
-                        full_new_child_path, baton,
+                child_editor = dir_editor.add_file(
+                        full_new_child_path, 
                     urlutils.join(self.repository.transport.svn_url, self.base_path, self.old_inv.id2path(child_ie.file_id)),
-                    self.base_revnum, 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_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
                 assert child_ie.file_id not in self.modified_files
-                child_baton = None
+                child_editor = None
 
             if child_ie.file_id in self.old_inv:
                 old_executable = self.old_inv[child_ie.file_id].executable
@@ -276,14 +274,13 @@ class SvnCommitBuilder(RootCommitBuilder):
                 old_special = False
                 old_executable = False
 
-            if child_baton is not None:
+            if child_editor is not None:
                 if old_executable != child_ie.executable:
                     if child_ie.executable:
                         value = properties.PROP_EXECUTABLE_VALUE
                     else:
                         value = None
-                    self.editor.change_file_prop(child_baton, 
-                            properties.PROP_EXECUTABLE, value, self.pool)
+                    child_editor.change_prop(properties.PROP_EXECUTABLE, value)
 
                 if old_special != (child_ie.kind == 'symlink'):
                     if child_ie.kind == 'symlink':
@@ -291,16 +288,15 @@ class SvnCommitBuilder(RootCommitBuilder):
                     else:
                         value = None
 
-                    self.editor.change_file_prop(child_baton, 
-                            properties.PROP_SPECIAL, value, self.pool)
+                    child_editor.change_prop(properties.PROP_SPECIAL, value)
 
             # handle the file
             if child_ie.file_id in self.modified_files:
                 self._file_process(child_ie.file_id, 
-                    self.modified_files[child_ie.file_id], child_baton)
+                    self.modified_files[child_ie.file_id], child_editor)
 
-            if child_baton is not None:
-                self.editor.close_file(child_baton, None, self.pool)
+            if child_editor is not None:
+                child_editor.close()
 
         # Loop over subdirectories of file_id in self.new_inventory
         for child_name in self.new_inventory[file_id].children:
@@ -312,42 +308,41 @@ class SvnCommitBuilder(RootCommitBuilder):
             # add them if they didn't exist in old_inv 
             if not child_ie.file_id in self.old_inv:
                 self.mutter('adding dir %r', child_ie.name)
-                child_baton = self.editor.add_directory(
+                child_editor = dir_editor.add_directory(
                     urlutils.join(self.branch.get_branch_path(), 
-                                  new_child_path), baton, None, -1, self.pool)
+                                  new_child_path))
 
             # copy if they existed at different location
             elif self.old_inv.id2path(child_ie.file_id) != new_child_path:
                 old_child_path = self.old_inv.id2path(child_ie.file_id)
-                self.mutter('copy dir %r -> %r', old_child_path, new_child_path)
-                child_baton = self.editor.add_directory(
+                self.mutter('copy dir %r -> %r',  old_child_path, new_child_path)
+                child_editor = dir_editor.add_directory(
                     urlutils.join(self.branch.get_branch_path(), new_child_path),
-                    baton, 
-                    urlutils.join(self.repository.transport.svn_url, self.base_path, old_child_path), self.base_revnum, self.pool)
+                    urlutils.join(self.repository.transport.svn_url, self.base_path, old_child_path), self.base_revnum)
 
             # open if they existed at the same location and 
             # the directory was touched
             elif self.new_inventory[child_ie.file_id].revision is None:
                 self.mutter('open dir %r', new_child_path)
 
-                child_baton = self.editor.open_directory(
+                child_editor = dir_editor.open_directory(
                         urlutils.join(self.branch.get_branch_path(), new_child_path), 
-                        baton, self.base_revnum, self.pool)
+                        self.base_revnum)
             else:
                 assert child_ie.file_id not in self.modified_dirs
                 continue
 
             # Handle this directory
             if child_ie.file_id in self.modified_dirs:
-                self._dir_process(new_child_path, child_ie.file_id, child_baton)
+                self._dir_process(new_child_path, child_ie.file_id, child_editor)
 
-            self.editor.close_directory(child_baton, self.pool)
+            child_editor.close()
 
-    def open_branch_batons(self, root, elements, existing_elements, 
+    def open_branch_editors(self, root, elements, existing_elements, 
                            base_path, base_rev, replace_existing):
-        """Open a specified directory given a baton for the repository root.
+        """Open a specified directory given an editor for the repository root.
 
-        :param root: Baton for the repository root
+        :param root: Editor for the repository root
         :param elements: List of directory names to open
         :param existing_elements: List of directory names that exist
         :param base_path: Path to base top-level branch on
@@ -362,8 +357,7 @@ class SvnCommitBuilder(RootCommitBuilder):
         # Open paths leading up to branch
         for i in range(0, len(elements)-1):
             # Does directory already exist?
-            ret.append(self.editor.open_directory(
-                "/".join(existing_elements[0:i+1]), ret[-1], -1, self.pool))
+            ret.append(ret[-1].open_directory("/".join(existing_elements[0:i+1]), -1))
 
         if (len(existing_elements) != len(elements) and
             len(existing_elements)+1 != len(elements)):
@@ -376,8 +370,8 @@ class SvnCommitBuilder(RootCommitBuilder):
         # branch_path.
         if (len(existing_elements) == len(elements) and 
             not replace_existing):
-            ret.append(self.editor.open_directory(
-                "/".join(elements), ret[-1], base_rev, self.pool))
+            ret.append(ret[-1].open_directory(
+                "/".join(elements), base_rev))
         else: # Branch has to be created
             # Already exists, old copy needs to be removed
             name = "/".join(elements)
@@ -385,14 +379,14 @@ class SvnCommitBuilder(RootCommitBuilder):
                 if name == "":
                     raise ChangesRootLHSHistory()
                 self.mutter("removing branch dir %r", name)
-                self.editor.delete_entry(name, -1, ret[-1])
+                ret[-1].delete_entry(name, -1)
             if base_path is not None:
                 base_url = urlutils.join(self.repository.transport.svn_url, base_path)
             else:
                 base_url = None
             self.mutter("adding branch dir %r", name)
-            ret.append(self.editor.add_directory(
-                name, ret[-1], base_url, base_rev, self.pool))
+            ret.append(ret[-1].add_directory(
+                name, base_url, base_rev))
 
         return ret
 
@@ -400,14 +394,17 @@ class SvnCommitBuilder(RootCommitBuilder):
         """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
-        
+            class CommitResult:
+                def __init__(self, revision, author, date):
+                    self.revision = revision
+                    self.author = author
+                    self.date = date
+            self.revision_metadata = CommitResult(revision, author, date)
+
         bp_parts = self.branch.get_branch_path().split("/")
         repository_latest_revnum = self.repository.get_latest_revnum()
         lock = self.repository.transport.lock_write(".")
@@ -450,59 +447,65 @@ class SvnCommitBuilder(RootCommitBuilder):
         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 properties.is_valid_property_name(prop):
                     warning("Setting property %r with invalid characters in name", prop)
+            conn = self.repository.transport.get_connection()
             try:
-                self.editor = self.repository.transport.get_commit_editor(
-                        self._svn_revprops, done, None, False)
-                self._svn_revprops = {}
-            except NotImplementedError:
-                if set_revprops:
-                    raise
-                # Try without bzr: revprops
-                self.editor = self.repository.transport.get_commit_editor({
-                    properties.PROP_REVISION_LOG: self._svn_revprops[properties.PROP_REVISION_LOG]},
-                    done, None, False)
-                del self._svn_revprops[properties.PROP_REVISION_LOG]
-
-            root = self.editor.open_root(self.base_revnum)
-
-            replace_existing = False
-            # See whether the base of the commit matches the lhs parent
-            # if not, we need to replace the existing directory
-            if len(bp_parts) == len(existing_bp_parts):
-                if self.base_path.strip("/") != "/".join(bp_parts).strip("/"):
-                    replace_existing = True
-                elif self.base_revnum < self.repository._log.find_latest_change(self.branch.get_branch_path(), repository_latest_revnum):
-                    replace_existing = True
-
-            if replace_existing and self.branch._get_append_revisions_only():
-                raise AppendRevisionsOnlyViolation(self.branch.base)
-
-            # TODO: Accept create_prefix argument (#118787)
-            branch_batons = self.open_branch_batons(root, bp_parts,
-                existing_bp_parts, self.base_path, self.base_revnum, 
-                replace_existing)
-
-            self._dir_process("", self.new_inventory.root.file_id, 
-                branch_batons[-1])
-
-            # Set all the revprops
-            for prop, value in self._svnprops.items():
-                if not properties.is_valid_property_name(prop):
-                    warning("Setting property %r with invalid characters in name", prop)
-                if value is not None:
-                    value = value.encode('utf-8')
-                self.editor.change_dir_prop(branch_batons[-1], prop, value, 
-                                            self.pool)
-                self.mutter("Setting root file property %r -> %r", prop, value)
-
-            for baton in reversed(branch_batons):
-                self.editor.close_directory(baton, self.pool)
-
-            self.editor.close()
+                try:
+                    self.editor = conn.get_commit_editor(
+                            self._svn_revprops, done, None, False)
+                    self._svn_revprops = {}
+                    self.editor_active = True
+                except NotImplementedError:
+                    if set_revprops:
+                        raise
+                    # Try without bzr: revprops
+                    self.editor = conn.get_commit_editor({
+                        properties.PROP_REVISION_LOG: self._svn_revprops[properties.PROP_REVISION_LOG]},
+                        done, None, False)
+                    del self._svn_revprops[properties.PROP_REVISION_LOG]
+
+                root = self.editor.open_root(self.base_revnum)
+
+                replace_existing = False
+                # See whether the base of the commit matches the lhs parent
+                # if not, we need to replace the existing directory
+                if len(bp_parts) == len(existing_bp_parts):
+                    if self.base_path.strip("/") != "/".join(bp_parts).strip("/"):
+                        replace_existing = True
+                    elif self.base_revnum < self.repository._log.find_latest_change(self.branch.get_branch_path(), repository_latest_revnum):
+                        replace_existing = True
+
+                if replace_existing and self.branch._get_append_revisions_only():
+                    raise AppendRevisionsOnlyViolation(self.branch.base)
+
+                # TODO: Accept create_prefix argument (#118787)
+                branch_editors = self.open_branch_editors(root, bp_parts,
+                    existing_bp_parts, self.base_path, self.base_revnum, 
+                    replace_existing)
+
+                self._dir_process("", self.new_inventory.root.file_id, 
+                    branch_editors[-1])
+
+                # Set all the revprops
+                for prop, value in self._svnprops.items():
+                    if not properties.is_valid_property_name(prop):
+                        warning("Setting property %r with invalid characters in name", prop)
+                    if value is not None:
+                        value = value.encode('utf-8')
+                    branch_editors[-1].change_prop(prop, value)
+                    self.mutter("Setting root file property %r -> %r", prop, value)
+
+                for dir_editor in reversed(branch_editors):
+                    dir_editor.close()
+
+                self.editor.close()
+                self.editor_active = False
+            finally:
+                if self.editor_active:
+                    self.editor.abort()
+                self.repository.transport.add_connection(conn)
         finally:
             lock.unlock()
 
@@ -524,7 +527,7 @@ class SvnCommitBuilder(RootCommitBuilder):
             if properties.PROP_REVISION_AUTHOR in override_svn_revprops:
                 new_revprops[properties.PROP_REVISION_AUTHOR] = self._committer.encode("utf-8")
             if properties.PROP_REVISION_DATE in override_svn_revprops:
-                new_revprops[properties.PROP_REVISION_DATE] = svn_time_to_cstring(1000000*self._timestamp)
+                new_revprops[properties.PROP_REVISION_DATE] = time_to_cstring(1000000*self._timestamp)
             set_svn_revprops(self.repository.transport, self.revision_metadata.revision, new_revprops)
 
         try:
@@ -553,6 +556,7 @@ class SvnCommitBuilder(RootCommitBuilder):
                 it is a candidate to commit.
         """
         self.new_inventory.add(ie)
+        return self._get_delta(ie, parent_invs[0], path), True
 
 
 def replay_delta(builder, old_tree, new_tree):
index 1148e40d9c85aa3dd5771f60f1845e5609816c66..090e6e178d570bbd60634833fd34c1c82d29857b 100644 (file)
--- a/config.py
+++ b/config.py
 from bzrlib import osutils, urlutils, trace
 from bzrlib.config import IniBasedConfig, config_dir, ensure_config_dir_exists, GlobalConfig, LocationConfig, Config, STORE_BRANCH, STORE_GLOBAL, STORE_LOCATION
 
+from bzrlib.plugins.svn.core import SubversionException
+
 import os
 
 from bzrlib.plugins.svn import properties
 
-import svn.core
-from svn.core import SubversionException
-
 # Settings are stored by UUID. 
 # Data stored includes default branching scheme and locations the repository 
 # was seen at.
index c856b96b98bfd7e7d54dfc212c2073214bb007ce..4f80d58f74e1b476379e4bbc8148272e667a3554 100644 (file)
@@ -25,9 +25,8 @@ from bzrlib.transport import get_transport
 
 from bzrlib.plugins.svn.errors import ERR_STREAM_MALFORMED_DATA
 from bzrlib.plugins.svn.format import get_rich_root_format
-
-import svn.core, svn.repos
-from svn.core import SubversionException
+from bzrlib.plugins.svn import core, repos
+from bzrlib.plugins.svn.core import SubversionException
 
 def transport_makedirs(transport, location_url):
     """Create missing directories.
@@ -63,7 +62,7 @@ def load_dumpfile(dumpfile, outputdir):
         created.
     """
     from cStringIO import StringIO
-    repos = svn.repos.svn_repos_create(outputdir, '', '', None, None)
+    r = repos.create(outputdir)
     if dumpfile.endswith(".gz"):
         import gzip
         file = gzip.GzipFile(dumpfile)
@@ -73,13 +72,12 @@ 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)
+        r.load_fs(file, StringIO(), repos.LOAD_UUID_DEFAULT)
     except SubversionException, (_, num):
         if num == ERR_STREAM_MALFORMED_DATA:
             raise NotDumpFile(dumpfile)
         raise
-    return repos
+    return r
 
 
 def convert_repository(source_repos, output_url, scheme=None, layout=None,
diff --git a/core.c b/core.c
new file mode 100644 (file)
index 0000000..626a632
--- /dev/null
+++ b/core.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <stdbool.h>
+#include <Python.h>
+#include <apr_general.h>
+#include <string.h>
+#include <svn_time.h>
+#include <svn_config.h>
+#include <svn_io.h>
+#include <svn_utf.h>
+
+#include "util.h"
+
+/** Convert a UNIX timestamp to a Subversion CString. */
+static PyObject *time_to_cstring(PyObject *self, PyObject *args)
+{
+       PyObject *ret;
+    apr_pool_t *pool;
+       apr_time_t when;
+       if (!PyArg_ParseTuple(args, "L", &when))
+               return NULL;
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    ret = PyString_FromString(svn_time_to_cstring(when, pool));
+    apr_pool_destroy(pool);
+    return ret;
+}
+
+/** Parse a Subversion time string and return a UNIX timestamp. */
+static PyObject *time_from_cstring(PyObject *self, PyObject *args)
+{
+    apr_time_t when;
+    apr_pool_t *pool;
+       char *data;
+
+       if (!PyArg_ParseTuple(args, "s", &data))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(pool, svn_time_from_cstring(&when, data, pool));
+    apr_pool_destroy(pool);
+    return PyLong_FromLongLong(when);
+}
+
+typedef struct {
+       PyObject_HEAD
+       svn_config_t *item;
+} ConfigObject;
+
+PyTypeObject Config_Type = {
+       PyObject_HEAD_INIT(NULL) 0,
+       .tp_name = "core.Config",
+       .tp_basicsize = sizeof(ConfigObject),
+       .tp_dealloc = (destructor)PyObject_Del,
+};
+
+static PyObject *get_config(PyObject *self, PyObject *args)
+{
+    apr_pool_t *pool;
+    apr_hash_t *cfg_hash = NULL;
+    apr_hash_index_t *idx;
+    const char *key;
+    svn_config_t *val;
+    apr_ssize_t klen;
+       char *config_dir = NULL;
+       PyObject *ret;
+
+       if (!PyArg_ParseTuple(args, "|z", &config_dir))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+
+    RUN_SVN_WITH_POOL(pool, 
+                                         svn_config_get_config(&cfg_hash, config_dir, pool));
+    ret = PyDict_New();
+    for (idx = apr_hash_first(pool, cfg_hash); idx != NULL; 
+                idx = apr_hash_next(idx)) {
+               ConfigObject *data;
+        apr_hash_this(idx, (const void **)&key, &klen, (void **)&val);
+               data = PyObject_New(ConfigObject, &Config_Type);
+               data->item = val;
+        PyDict_SetItemString(ret, key, (PyObject *)data);
+       }
+    apr_pool_destroy(pool);
+    return ret;
+}
+
+
+static PyMethodDef core_methods[] = {
+       { "get_config", get_config, METH_VARARGS, NULL },
+       { "time_from_cstring", time_from_cstring, METH_VARARGS, NULL },
+       { "time_to_cstring", time_to_cstring, METH_VARARGS, NULL },
+       { NULL, }
+};
+
+void initcore(void)
+{
+       static apr_pool_t *pool;
+       PyObject *mod;
+
+       if (PyType_Ready(&Config_Type) < 0)
+               return;
+
+       apr_initialize();
+       pool = Pool();
+       if (pool == NULL)
+               return;
+       svn_utf_initialize(pool);
+
+       mod = Py_InitModule3("core", core_methods, "Core functions");
+       if (mod == NULL)
+               return;
+
+       PyModule_AddIntConstant(mod, "NODE_DIR", svn_node_dir);
+       PyModule_AddIntConstant(mod, "NODE_FILE", svn_node_file);
+       PyModule_AddIntConstant(mod, "NODE_UNKNOWN", svn_node_unknown);
+       PyModule_AddIntConstant(mod, "NODE_NONE", svn_node_none);
+
+       PyModule_AddObject(mod, "SubversionException", 
+                                          PyErr_NewException("core.SubversionException", NULL, NULL));
+}
diff --git a/delta.py b/delta.py
new file mode 100644 (file)
index 0000000..575da42
--- /dev/null
+++ b/delta.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2005-2006 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""Subversion delta operations."""
+
+def apply_txdelta_handler(sbuf, target_stream):
+    def apply_window(window):
+        (sview_offset, sview_len, tview_len, src_ops, ops, new_data) = window
+        sview = sbuf[sview_offset:sview_offset+sview_len]
+        tview = txdelta_apply_ops(src_ops, ops, new_data, sview)
+        assert len(tview) == tview_len
+        target_stream.write(tview)
+    return apply_window
+
+def txdelta_apply_ops(src_ops, ops, new_data, sview):
+    tview = ""
+    for (action, offset, length) in ops:
+        if action == 0:
+            # Copy from source area.
+            tview += sview[offset:offset+length]
+        elif action == 1:
+            for i in xrange(length):
+                tview += tview[offset+i]
+        elif action == 2:
+            tview += new_data[offset:offset+length]
+        else:
+            raise Exception("Invalid delta instruction code")
+
+    return tview
diff --git a/editor.c b/editor.c
new file mode 100644 (file)
index 0000000..218b568
--- /dev/null
+++ b/editor.c
@@ -0,0 +1,451 @@
+/* Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <stdbool.h>
+#include <Python.h>
+#include <apr_general.h>
+#include <svn_types.h>
+#include <svn_delta.h>
+
+#include "editor.h"
+#include "util.h"
+
+typedef struct {
+       PyObject_HEAD
+    const svn_delta_editor_t *editor;
+    void *baton;
+    apr_pool_t *pool;
+       void (*done_cb) (void *baton);
+       void *done_baton;
+} EditorObject;
+
+PyObject *new_editor_object(const svn_delta_editor_t *editor, void *baton, apr_pool_t *pool, PyTypeObject *type, void (*done_cb) (void *), void *done_baton)
+{
+       EditorObject *obj = PyObject_New(EditorObject, type);
+       if (obj == NULL)
+               return NULL;
+       obj->editor = editor;
+    obj->baton = baton;
+       obj->pool = pool;
+       obj->done_cb = done_cb;
+       obj->done_baton = done_baton;
+       return (PyObject *)obj;
+}
+
+static void py_editor_dealloc(PyObject *self)
+{
+       EditorObject *editor = (EditorObject *)self;
+       apr_pool_destroy(editor->pool);
+       PyObject_Del(self);
+}
+
+
+PyTypeObject TxDeltaWindowHandler_Type = {
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_basicsize = sizeof(TxDeltaWindowHandlerObject),
+       .tp_name = "ra.TxDeltaWindowHandler",
+       .tp_call = NULL, /* FIXME */
+       .tp_dealloc = (destructor)PyObject_Del
+};
+
+static PyObject *py_file_editor_apply_textdelta(PyObject *self, PyObject *args)
+{
+       EditorObject *editor = (EditorObject *)self;
+       char *c_base_checksum = NULL;
+       svn_txdelta_window_handler_t txdelta_handler;
+       void *txdelta_baton;
+       TxDeltaWindowHandlerObject *py_txdelta;
+
+       if (!FileEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "|z", &c_base_checksum))
+               return NULL;
+       if (!check_error(editor->editor->apply_textdelta(editor->baton,
+                               c_base_checksum, editor->pool, 
+                               &txdelta_handler, &txdelta_baton)))
+               return NULL;
+       py_txdelta = PyObject_New(TxDeltaWindowHandlerObject, &TxDeltaWindowHandler_Type);
+       py_txdelta->txdelta_handler = txdelta_handler;
+       py_txdelta->txdelta_baton = txdelta_baton;
+       return (PyObject *)py_txdelta;
+}
+
+static PyObject *py_file_editor_change_prop(PyObject *self, PyObject *args)
+{
+       EditorObject *editor = (EditorObject *)self;
+       char *name;
+       svn_string_t c_value;
+
+       if (!FileEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "sz#", &name, &c_value.data, &c_value.len))
+               return NULL;
+       if (!check_error(editor->editor->change_file_prop(editor->baton, name, 
+                               &c_value, editor->pool)))
+               return NULL;
+       Py_RETURN_NONE;
+}
+
+static PyObject *py_file_editor_close(PyObject *self, PyObject *args)
+{
+       EditorObject *editor = (EditorObject *)self;
+       char *c_checksum = NULL;
+
+       if (!FileEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "|z", &c_checksum))
+               return NULL;
+       if (!check_error(editor->editor->close_file(editor->baton, c_checksum, 
+                    editor->pool)))
+               return NULL;
+       Py_RETURN_NONE;
+}
+
+static PyMethodDef py_file_editor_methods[] = {
+       { "change_prop", py_file_editor_change_prop, METH_VARARGS, NULL },
+       { "close", py_file_editor_close, METH_VARARGS, NULL },
+       { "apply_textdelta", py_file_editor_apply_textdelta, METH_VARARGS, NULL },
+       { NULL }
+};
+
+PyTypeObject FileEditor_Type = { 
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "ra.FileEditor",
+       .tp_basicsize = sizeof(EditorObject),
+       .tp_methods = py_file_editor_methods,
+       .tp_dealloc = (destructor)PyObject_Del,
+};
+
+static PyObject *py_dir_editor_delete_entry(PyObject *self, PyObject *args)
+{
+       EditorObject *editor = (EditorObject *)self;
+       char *path; 
+       svn_revnum_t revision = -1;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "s|l", &path, &revision))
+               return NULL;
+
+       if (!check_error(editor->editor->delete_entry(path, revision, editor->baton,
+                                             editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *py_dir_editor_add_directory(PyObject *self, PyObject *args)
+{
+       char *path;
+       char *copyfrom_path=NULL; 
+       int copyfrom_rev=-1;
+       void *child_baton;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "s|zl", &path, &copyfrom_path, &copyfrom_rev))
+               return NULL;
+
+       if (!check_error(editor->editor->add_directory(path, editor->baton,
+                    copyfrom_path, copyfrom_rev, editor->pool, &child_baton)))
+               return NULL;
+
+    return new_editor_object(editor->editor, child_baton, editor->pool, 
+                                                        &DirectoryEditor_Type, NULL, NULL);
+}
+
+static PyObject *py_dir_editor_open_directory(PyObject *self, PyObject *args)
+{
+       char *path;
+       EditorObject *editor = (EditorObject *)self;
+       int base_revision=-1;
+       void *child_baton;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "s|l", &path, &base_revision))
+               return NULL;
+
+       if (!check_error(editor->editor->open_directory(path, editor->baton,
+                    base_revision, editor->pool, &child_baton)))
+               return NULL;
+
+    return new_editor_object(editor->editor, child_baton, editor->pool, 
+                                                        &DirectoryEditor_Type, NULL, NULL);
+}
+
+static PyObject *py_dir_editor_change_prop(PyObject *self, PyObject *args)
+{
+       char *name;
+       svn_string_t c_value, *p_c_value;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "sz#", &name, &c_value.data, &c_value.len))
+               return NULL;
+
+       p_c_value = &c_value;
+
+       if (!check_error(editor->editor->change_dir_prop(editor->baton, name, 
+                    p_c_value, editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *py_dir_editor_close(PyObject *self)
+{
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+    if (!check_error(editor->editor->close_directory(editor->baton, 
+                                                                                                        editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *py_dir_editor_absent_directory(PyObject *self, PyObject *args)
+{
+       char *path;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!Editor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+    
+       if (!check_error(editor->editor->absent_directory(path, editor->baton, 
+                    editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *py_dir_editor_add_file(PyObject *self, PyObject *args)
+{
+       char *path, *copy_path=NULL;
+       int copy_rev=-1;
+       void *file_baton;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "s|zl", &path, &copy_path, &copy_rev))
+               return NULL;
+
+       if (!check_error(editor->editor->add_file(path, editor->baton, copy_path,
+                    copy_rev, editor->pool, &file_baton)))
+               return NULL;
+
+       return new_editor_object(editor->editor, file_baton, editor->pool,
+                                                        &FileEditor_Type, NULL, NULL);
+}
+
+static PyObject *py_dir_editor_open_file(PyObject *self, PyObject *args)
+{
+       char *path;
+       int base_revision=-1;
+       void *file_baton;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "s|l", &path, &base_revision))
+               return NULL;
+
+       if (!check_error(editor->editor->open_file(path, editor->baton, 
+                    base_revision, editor->pool, &file_baton)))
+               return NULL;
+
+       return new_editor_object(editor->editor, file_baton, editor->pool,
+                                                        &FileEditor_Type, NULL, NULL);
+}
+
+static PyObject *py_dir_editor_absent_file(PyObject *self, PyObject *args)
+{
+       char *path;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!DirectoryEditor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+       if (!check_error(editor->editor->absent_file(path, editor->baton, editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyMethodDef py_dir_editor_methods[] = {
+       { "absent_file", py_dir_editor_absent_file, METH_VARARGS, NULL },
+       { "absent_directory", py_dir_editor_absent_directory, METH_VARARGS, NULL },
+       { "delete_entry", py_dir_editor_delete_entry, METH_VARARGS, NULL },
+       { "add_file", py_dir_editor_add_file, METH_VARARGS, NULL },
+       { "open_file", py_dir_editor_open_file, METH_VARARGS, NULL },
+       { "add_directory", py_dir_editor_add_directory, METH_VARARGS, NULL },
+       { "open_directory", py_dir_editor_open_directory, METH_VARARGS, NULL },
+       { "close", (PyCFunction)py_dir_editor_close, METH_NOARGS, NULL },
+       { "change_prop", py_dir_editor_change_prop, METH_VARARGS, NULL },
+
+       { NULL, }
+};
+
+PyTypeObject DirectoryEditor_Type = { 
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "ra.DirEditor",
+       .tp_basicsize = sizeof(EditorObject),
+       .tp_methods = py_dir_editor_methods,
+       .tp_dealloc = (destructor)PyObject_Del,
+};
+
+static PyObject *py_editor_set_target_revision(PyObject *self, PyObject *args)
+{
+       int target_revision;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!Editor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "i", &target_revision))
+               return NULL;
+
+       if (!check_error(editor->editor->set_target_revision(editor->baton,
+                    target_revision, editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+    
+static PyObject *py_editor_open_root(PyObject *self, PyObject *args)
+{
+       svn_revnum_t base_revision=-1;
+       void *root_baton;
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!Editor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (!PyArg_ParseTuple(args, "|l:open_root", &base_revision))
+               return NULL;
+
+    if (!check_error(editor->editor->open_root(editor->baton, base_revision,
+                    editor->pool, &root_baton)))
+               return NULL;
+
+       return new_editor_object(editor->editor, root_baton, editor->pool,
+                                                        &DirectoryEditor_Type, NULL, NULL);
+}
+
+static PyObject *py_editor_close(PyObject *self)
+{
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!Editor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (editor->done_cb != NULL)
+               editor->done_cb(editor->done_baton);
+
+       if (!check_error(editor->editor->close_edit(editor->baton, editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *py_editor_abort(PyObject *self)
+{
+       EditorObject *editor = (EditorObject *)self;
+
+       if (!Editor_Check(self)) {
+               PyErr_BadArgument();
+               return NULL;
+       }
+
+       if (editor->done_cb != NULL)
+               editor->done_cb(editor->done_baton);
+       
+       if (!check_error(editor->editor->abort_edit(editor->baton, editor->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyMethodDef py_editor_methods[] = { 
+       { "abort", (PyCFunction)py_editor_abort, METH_NOARGS, NULL },
+       { "close", (PyCFunction)py_editor_close, METH_NOARGS, NULL },
+       { "open_root", py_editor_open_root, METH_VARARGS, NULL },
+       { "set_target_revision", py_editor_set_target_revision, METH_VARARGS, NULL },
+       { NULL, }
+};
+
+PyTypeObject Editor_Type = { 
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "ra.Editor",
+       .tp_basicsize = sizeof(EditorObject),
+       .tp_methods = py_editor_methods,
+       .tp_dealloc = py_editor_dealloc,
+};
+
+
diff --git a/editor.h b/editor.h
new file mode 100644 (file)
index 0000000..4eb2986
--- /dev/null
+++ b/editor.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _BZR_SVN_EDITOR_H_
+#define _BZR_SVN_EDITOR_H_
+
+#pragma GCC visibility push(hidden)
+
+PyAPI_DATA(PyTypeObject) DirectoryEditor_Type;
+PyAPI_DATA(PyTypeObject) FileEditor_Type;
+PyAPI_DATA(PyTypeObject) Editor_Type;
+PyAPI_DATA(PyTypeObject) TxDeltaWindowHandler_Type;
+PyObject *new_editor_object(const svn_delta_editor_t *editor, void *baton, apr_pool_t *pool, PyTypeObject *type, void (*done_cb) (void *baton), void *done_baton);
+
+#define DirectoryEditor_Check(op) PyObject_TypeCheck(op, &DirectoryEditor_Type)
+#define FileEditor_Check(op) PyObject_TypeCheck(op, &FileEditor_Type)
+#define Editor_Check(op) PyObject_TypeCheck(op, &Editor_Type)
+#define TxDeltaWindowHandler_Check(op) PyObject_TypeCheck(op, &TxDeltaWindowHandler_Type)
+
+typedef struct {
+       PyObject_HEAD
+       svn_txdelta_window_handler_t txdelta_handler;
+       void *txdelta_baton;
+} TxDeltaWindowHandlerObject;
+
+#pragma GCC visibility pop
+
+#endif /* _BZR_SVN_EDITOR_H_ */
index 5be2e6f0bddb2530b4699e0d6262f0ba2a591772..168dede4a6fad647e3a750bc654609adafa3e8ab 100644 (file)
--- a/errors.py
+++ b/errors.py
@@ -21,7 +21,7 @@ from bzrlib.errors import (BzrError, ConnectionError, ConnectionReset,
                            TransportError, UnexpectedEndOfContainerError)
 
 import urllib
-import svn.core
+from bzrlib.plugins.svn import core
 
 
 class InvalidExternalsDescription(BzrError):
@@ -56,7 +56,6 @@ ERR_CANCELLED = 200015
 ERR_WC_UNSUPPORTED_FORMAT = 155021
 
 
-
 class NotSvnBranchPath(NotBranchError):
     """Error raised when a path was specified that did not exist."""
     _fmt = """%(path)s is not a valid Subversion branch path. 
@@ -129,7 +128,7 @@ def convert_svn_error(unbound):
     def convert(*args, **kwargs):
         try:
             return unbound(*args, **kwargs)
-        except svn.core.SubversionException, e:
+        except core.SubversionException, e:
             raise convert_error(e)
 
     convert.__doc__ = unbound.__doc__
@@ -137,16 +136,6 @@ def convert_svn_error(unbound):
     return convert
 
 
-class NoCheckoutSupport(BzrError):
-
-    _fmt = 'Subversion version too old for working tree support.'
-
-
-class LocalCommitsUnsupported(BzrError):
-
-    _fmt = 'Local commits are not supported for lightweight Subversion checkouts.'
-
-
 class InvalidPropertyValue(BzrError):
     _fmt = 'Invalid property value for Subversion property %(property)s: %(msg)s'
 
index aa8dcd1bb86ed4427264302346ff9bace959e2c3..f78aa4e374900f0b10e439d71afb9b5ee167cd73 100644 (file)
--- a/fetch.py
+++ b/fetch.py
@@ -25,9 +25,7 @@ from bzrlib.trace import mutter
 from cStringIO import StringIO
 import md5
 
-from svn.core import Pool
-import svn.core
-
+from bzrlib.plugins.svn.delta import apply_txdelta_handler
 from bzrlib.plugins.svn import properties
 from bzrlib.plugins.svn.errors import InvalidFileName
 from bzrlib.plugins.svn.logwalker import lazy_dict
@@ -39,7 +37,7 @@ from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_MERGE,
                      parse_revision_metadata)
 from bzrlib.plugins.svn.repository import SvnRepository, SvnRepositoryFormat
 from bzrlib.plugins.svn.svk import SVN_PROP_SVK_MERGE
-from bzrlib.plugins.svn.tree import (apply_txdelta_handler, parse_externals_description, 
+from bzrlib.plugins.svn.tree import (parse_externals_description, 
                   inventory_add_external)
 
 
@@ -86,7 +84,7 @@ def check_filename(path):
         raise InvalidFileName(path)
 
 
-class RevisionBuildEditor(svn.delta.Editor):
+class RevisionBuildEditor:
     """Implementation of the Subversion commit editor interface that builds a 
     Bazaar revision.
     """
@@ -95,6 +93,9 @@ class RevisionBuildEditor(svn.delta.Editor):
         self.source = source
         self.transact = target.get_transaction()
 
+    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)
@@ -103,7 +104,6 @@ class RevisionBuildEditor(svn.delta.Editor):
         self.dir_baserev = {}
         self._revinfo = None
         self._premature_deletes = set()
-        self.pool = Pool()
         self.old_inventory = prev_inventory
         self.inventory = prev_inventory.copy()
         self._start_revision()
@@ -134,17 +134,17 @@ class RevisionBuildEditor(svn.delta.Editor):
 
         return (rev, signature)
 
-    def open_root(self, base_revnum, baton):
+    def open_root(self, base_revnum):
         if self.old_inventory.root is None:
             # First time the root is set
             old_file_id = None
             file_id = self.mapping.generate_file_id(self.source.uuid, self.revnum, self.branch_path, u"")
-            self.dir_baserev[file_id] = []
+            file_parents = []
         else:
             assert self.old_inventory.root.revision is not None
             old_file_id = self.old_inventory.root.file_id
             file_id = self._get_id_map().get("", old_file_id)
-            self.dir_baserev[file_id] = [self.old_inventory.root.revision]
+            file_parents = [self.old_inventory.root.revision]
 
         if self.inventory.root is not None and \
                 file_id == self.inventory.root.file_id:
@@ -152,7 +152,28 @@ class RevisionBuildEditor(svn.delta.Editor):
         else:
             ie = self.inventory.add_path("", 'directory', file_id)
         ie.revision = self.revid
-        return (old_file_id, file_id)
+        return DirectoryBuildEditor(self, old_file_id, file_id, file_parents)
+
+    def close(self):
+        pass
+
+    def _store_directory(self, file_id, parents):
+        raise NotImplementedError(self._store_directory)
+
+    def _get_file_data(self, file_id, revid):
+        raise NotImplementedError(self._get_file_data)
+
+    def _finish_commit(self):
+        raise NotImplementedError(self._finish_commit)
+
+    def abort(self):
+        pass
+
+    def _start_revision(self):
+        pass
+
+    def _store_file(self, file_id, lines, parents):
+        raise NotImplementedError(self._store_file)
 
     def _get_existing_id(self, old_parent_id, new_parent_id, path):
         assert isinstance(path, unicode)
@@ -186,82 +207,78 @@ class RevisionBuildEditor(svn.delta.Editor):
             return
         self.inventory.rename(file_id, parent_id, urlutils.basename(path))
 
-    def delete_entry(self, path, revnum, (old_parent_id, new_parent_id), pool):
-        assert isinstance(path, str)
-        path = path.decode("utf-8")
-        if path in self._premature_deletes:
-            # Delete recursively
-            self._premature_deletes.remove(path)
-            for p in self._premature_deletes.copy():
-                if p.startswith("%s/" % path):
-                    self._premature_deletes.remove(p)
-        else:
-            self.inventory.remove_recursive_id(self._get_old_id(old_parent_id, path))
+class DirectoryBuildEditor:
+    def __init__(self, editor, old_id, new_id, parent_revids=[]):
+        self.editor = editor
+        self.old_id = old_id
+        self.new_id = new_id
+        self.parent_revids = parent_revids
 
-    def close_directory(self, (old_id, new_id)):
-        self.inventory[new_id].revision = self.revid
+    def close(self):
+        self.editor.inventory[self.new_id].revision = self.editor.revid
+        self.editor._store_directory(self.new_id, self.parent_revids)
 
-        # Only record root if the target repository supports it
-        self._store_directory(new_id, self.dir_baserev[new_id])
+        if self.new_id == self.editor.inventory.root.file_id:
+            assert len(self.editor._premature_deletes) == 0
+            self.editor._finish_commit()
 
-    def add_directory(self, path, (old_parent_id, new_parent_id), copyfrom_path, copyfrom_revnum, 
-                      pool):
+    def add_directory(self, path, copyfrom_path=None, copyfrom_revnum=-1):
         assert isinstance(path, str)
         path = path.decode("utf-8")
         check_filename(path)
-        file_id = self._get_new_id(new_parent_id, path)
+        file_id = self.editor._get_new_id(self.new_id, path)
 
-        self.dir_baserev[file_id] = []
-        if file_id in self.inventory:
+        if file_id in self.editor.inventory:
             # This directory was moved here from somewhere else, but the 
             # other location hasn't been removed yet. 
             if copyfrom_path is None:
                 # This should ideally never happen!
-                copyfrom_path = self.old_inventory.id2path(file_id)
+                copyfrom_path = self.editor.old_inventory.id2path(file_id)
                 mutter('no copyfrom path set, assuming %r', copyfrom_path)
-            assert copyfrom_path == self.old_inventory.id2path(file_id)
-            assert copyfrom_path not in self._premature_deletes
-            self._premature_deletes.add(copyfrom_path)
-            self._rename(file_id, new_parent_id, path)
-            ie = self.inventory[file_id]
+            assert copyfrom_path == self.editor.old_inventory.id2path(file_id)
+            assert copyfrom_path not in self.editor._premature_deletes
+            self.editor._premature_deletes.add(copyfrom_path)
+            self.editor._rename(file_id, self.new_id, path)
+            ie = self.editor.inventory[file_id]
             old_file_id = file_id
         else:
             old_file_id = None
-            ie = self.inventory.add_path(path, 'directory', file_id)
-        ie.revision = self.revid
+            ie = self.editor.inventory.add_path(path, 'directory', file_id)
+        ie.revision = self.editor.revid
 
-        return (old_file_id, file_id)
+        return DirectoryBuildEditor(self.editor, old_file_id, file_id)
 
-    def open_directory(self, path, (old_parent_id, new_parent_id), base_revnum, pool):
+    def open_directory(self, path, base_revnum):
         assert isinstance(path, str)
         path = path.decode("utf-8")
-        assert base_revnum >= 0
-        base_file_id = self._get_old_id(old_parent_id, path)
-        base_revid = self.old_inventory[base_file_id].revision
-        file_id = self._get_existing_id(old_parent_id, new_parent_id, path)
+        assert isinstance(base_revnum, int)
+        base_file_id = self.editor._get_old_id(self.old_id, path)
+        base_revid = self.editor.old_inventory[base_file_id].revision
+        file_id = self.editor._get_existing_id(self.old_id, self.new_id, path)
         if file_id == base_file_id:
-            self.dir_baserev[file_id] = [base_revid]
-            ie = self.inventory[file_id]
+            file_parents = [base_revid]
+            ie = self.editor.inventory[file_id]
         else:
             # Replace if original was inside this branch
             # change id of base_file_id to file_id
-            ie = self.inventory[base_file_id]
+            ie = self.editor.inventory[base_file_id]
             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:
             # Replay lazy_dict, since it may be more expensive
-            if type(self.revmeta.fileprops) != dict:
-                self.revmeta.fileprops = {}
-            self.revmeta.fileprops[name] = value
+            if type(self.editor.revmeta.fileprops) != dict:
+                self.editor.revmeta.fileprops = {}
+            self.editor.revmeta.fileprops[name] = value
 
         if name in (properties.PROP_ENTRY_COMMITTED_DATE,
                     properties.PROP_ENTRY_COMMITTED_REV,
@@ -275,10 +292,78 @@ class RevisionBuildEditor(svn.delta.Editor):
         elif name.startswith(properties.PROP_PREFIX):
             mutter('unsupported dir property %r', name)
 
-    def change_file_prop(self, id, name, value, pool):
+    def add_file(self, path, copyfrom_path=None, copyfrom_revnum=-1):
+        assert isinstance(path, str)
+        path = path.decode("utf-8")
+        check_filename(path)
+        file_id = self.editor._get_new_id(self.new_id, path)
+        if file_id in self.editor.inventory:
+            # This file was moved here from somewhere else, but the 
+            # other location hasn't been removed yet. 
+            if copyfrom_path is None:
+                # This should ideally never happen
+                copyfrom_path = self.editor.old_inventory.id2path(file_id)
+                mutter('no copyfrom path set, assuming %r' % copyfrom_path)
+            assert copyfrom_path == self.editor.old_inventory.id2path(file_id)
+            assert copyfrom_path not in self.editor._premature_deletes
+            self.editor._premature_deletes.add(copyfrom_path)
+            # No need to rename if it's already in the right spot
+            self.editor._rename(file_id, self.new_id, path)
+        return FileBuildEditor(self.editor, path, file_id)
+
+    def open_file(self, path, base_revnum):
+        assert isinstance(path, str)
+        path = path.decode("utf-8")
+        base_file_id = self.editor._get_old_id(self.old_id, path)
+        base_revid = self.editor.old_inventory[base_file_id].revision
+        file_id = self.editor._get_existing_id(self.old_id, self.new_id, path)
+        is_symlink = (self.editor.inventory[base_file_id].kind == 'symlink')
+        file_data = self.editor._get_file_data(base_file_id, base_revid)
+        if file_id == base_file_id:
+            file_parents = [base_revid]
+        else:
+            # Replace
+            del self.editor.inventory[base_file_id]
+            file_parents = []
+        return FileBuildEditor(self.editor, path, file_id, 
+                               file_parents, file_data, is_symlink=is_symlink)
+
+    def delete_entry(self, path, revnum):
+        assert isinstance(path, str)
+        path = path.decode("utf-8")
+        if path in self.editor._premature_deletes:
+            # Delete recursively
+            self.editor._premature_deletes.remove(path)
+            for p in self.editor._premature_deletes.copy():
+                if p.startswith("%s/" % path):
+                    self.editor._premature_deletes.remove(p)
+        else:
+            self.editor.inventory.remove_recursive_id(self.editor._get_old_id(self.old_id, path))
+
+class FileBuildEditor:
+    def __init__(self, editor, path, file_id, file_parents=[], data="", 
+                 is_symlink=False):
+        self.path = path
+        self.editor = editor
+        self.file_id = file_id
+        self.file_data = data
+        self.is_symlink = is_symlink
+        self.file_parents = file_parents
+        self.is_executable = None
+        self.file_stream = None
+
+    def apply_textdelta(self, base_checksum=None):
+        actual_checksum = md5.new(self.file_data).hexdigest()
+        assert (base_checksum is None or base_checksum == actual_checksum,
+            "base checksum mismatch: %r != %r" % (base_checksum, 
+                                                  actual_checksum))
+        self.file_stream = StringIO()
+        return apply_txdelta_handler(self.file_data, self.file_stream)
+
+    def change_prop(self, name, value):
         if name == properties.PROP_EXECUTABLE: 
             # You'd expect executable to match 
-            # properties.PROP_EXECUTABLE_VALUE, but that's not 
+            # constants.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)
@@ -286,8 +371,6 @@ class RevisionBuildEditor(svn.delta.Editor):
             self.is_symlink = (value != None)
         elif name == properties.PROP_ENTRY_COMMITTED_REV:
             self.last_file_rev = int(value)
-        elif name == properties.PROP_EXTERNALS:
-            mutter('svn:externals property on file!')
         elif name in (properties.PROP_ENTRY_COMMITTED_DATE,
                       properties.PROP_ENTRY_LAST_AUTHOR,
                       properties.PROP_ENTRY_LOCK_TOKEN,
@@ -296,54 +379,14 @@ class RevisionBuildEditor(svn.delta.Editor):
             pass
         elif name.startswith(properties.PROP_WC_PREFIX):
             pass
+        elif name == properties.PROP_EXTERNALS:
+            mutter('svn:externals property on file!')
         elif (name.startswith(properties.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):
-        assert isinstance(path, str)
-        path = path.decode("utf-8")
-        check_filename(path)
-        self.is_symlink = False
-        self.is_executable = None
-        self.file_data = ""
-        self.file_parents = []
-        self.file_stream = None
-        self.file_id = self._get_new_id(new_parent_id, path)
-        if self.file_id in self.inventory:
-            # This file was moved here from somewhere else, but the 
-            # other location hasn't been removed yet. 
-            if copyfrom_path is None:
-                # This should ideally never happen
-                copyfrom_path = self.old_inventory.id2path(self.file_id)
-                mutter('no copyfrom path set, assuming %r', copyfrom_path)
-            assert copyfrom_path == self.old_inventory.id2path(self.file_id)
-            assert copyfrom_path not in self._premature_deletes
-            self._premature_deletes.add(copyfrom_path)
-            # No need to rename if it's already in the right spot
-            self._rename(self.file_id, new_parent_id, path)
-        return path
-
-    def open_file(self, path, (old_parent_id, new_parent_id), base_revnum, pool):
-        assert isinstance(path, str)
-        path = path.decode("utf-8")
-        base_file_id = self._get_old_id(old_parent_id, path)
-        base_revid = self.old_inventory[base_file_id].revision
-        self.file_id = self._get_existing_id(old_parent_id, new_parent_id, path)
-        self.is_executable = None
-        self.is_symlink = (self.inventory[base_file_id].kind == 'symlink')
-        self.file_data = self._get_file_data(base_file_id, base_revid)
-        self.file_stream = None
-        if self.file_id == base_file_id:
-            self.file_parents = [base_revid]
-        else:
-            # Replace
-            del self.inventory[base_file_id]
-            self.file_parents = []
-        return path
-
-    def close_file(self, path, checksum):
-        assert isinstance(path, unicode)
+    def close(self, checksum=None):
+        assert isinstance(self.path, unicode)
         if self.file_stream is not None:
             self.file_stream.seek(0)
             lines = osutils.split_lines(self.file_stream.read())
@@ -354,23 +397,23 @@ class RevisionBuildEditor(svn.delta.Editor):
         actual_checksum = md5_strings(lines)
         assert checksum is None or checksum == actual_checksum
 
-        self._store_file(self.file_id, lines, self.file_parents)
+        self.editor._store_file(self.file_id, lines, self.file_parents)
 
         assert self.is_symlink in (True, False)
 
-        if self.file_id in self.inventory:
-            del self.inventory[self.file_id]
+        if self.file_id in self.editor.inventory:
+            del self.editor.inventory[self.file_id]
 
         if self.is_symlink:
-            ie = self.inventory.add_path(path, 'symlink', self.file_id)
+            ie = self.editor.inventory.add_path(self.path, 'symlink', self.file_id)
             ie.symlink_target = lines[0][len("link "):]
             ie.text_sha1 = None
             ie.text_size = None
             ie.executable = False
-            ie.revision = self.revid
+            ie.revision = self.editor.revid
         else:
-            ie = self.inventory.add_path(path, 'file', self.file_id)
-            ie.revision = self.revid
+            ie = self.editor.inventory.add_path(self.path, 'file', self.file_id)
+            ie.revision = self.editor.revid
             ie.kind = 'file'
             ie.symlink_target = None
             ie.text_sha1 = osutils.sha_strings(lines)
@@ -379,41 +422,8 @@ class RevisionBuildEditor(svn.delta.Editor):
             if self.is_executable is not None:
                 ie.executable = self.is_executable
 
-
         self.file_stream = None
 
-    def close_edit(self):
-        assert len(self._premature_deletes) == 0
-        self._finish_commit()
-        self.pool.destroy()
-
-    def apply_textdelta(self, file_id, base_checksum):
-        actual_checksum = md5.new(self.file_data).hexdigest(),
-        assert (base_checksum is None or base_checksum == actual_checksum,
-            "base checksum mismatch: %r != %r" % (base_checksum, 
-                                                  actual_checksum))
-        self.file_stream = StringIO()
-        return apply_txdelta_handler(StringIO(self.file_data), 
-                                     self.file_stream, self.pool)
-
-    def _store_file(self, file_id, lines, parents):
-        raise NotImplementedError(self._store_file)
-
-    def _store_directory(self, file_id, parents):
-        raise NotImplementedError(self._store_directory)
-
-    def _get_file_data(self, file_id, revid):
-        raise NotImplementedError(self._get_file_data)
-
-    def _finish_commit(self):
-        raise NotImplementedError(self._finish_commit)
-
-    def abort_edit(self):
-        pass
-
-    def _start_revision(self):
-        pass
-
 
 class WeaveRevisionBuildEditor(RevisionBuildEditor):
     """Subversion commit editor that can write to a weave-based repository.
@@ -452,7 +462,7 @@ class WeaveRevisionBuildEditor(RevisionBuildEditor):
         self.target.commit_write_group()
         self._write_group_active = False
 
-    def abort_edit(self):
+    def abort(self):
         if self._write_group_active:
             self.target.abort_write_group()
             self._write_group_active = False
@@ -587,14 +597,6 @@ class InterFromSvnRepository(InterRepository):
         """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.
-
-        :param revids: Revision ids to copy.
-        :param pb: Optional progress bar
-        """
-        raise NotImplementedError(self._copy_revisions_replay)
-
     def _fetch_switch(self, repos_root, revids, pb=None):
         """Copy a set of related revisions using svn.ra.switch.
 
@@ -638,14 +640,14 @@ class InterFromSvnRepository(InterRepository):
                                                        editor.branch_path)
 
                             conn = self.source.transport.connections.get(branch_url)
-                            reporter = conn.do_update(editor.revnum, True, 
+                            reporter = conn.do_update(editor.revnum, "", True, 
                                                            editor)
 
                             try:
                                 # Report status of existing paths
                                 reporter.set_path("", editor.revnum, True, None)
                             except:
-                                reporter.abort_report()
+                                reporter.abort()
                                 raise
                         else:
                             (parent_branch, parent_revnum, mapping) = \
@@ -653,25 +655,25 @@ class InterFromSvnRepository(InterRepository):
                             conn = self.source.transport.connections.get(urlutils.join(repos_root, parent_branch))
 
                             if parent_branch != editor.branch_path:
-                                reporter = conn.do_switch(editor.revnum, True, 
+                                reporter = conn.do_switch(editor.revnum, "", True, 
                                     urlutils.join(repos_root, editor.branch_path), 
                                     editor)
                             else:
-                                reporter = conn.do_update(editor.revnum, True, editor)
+                                reporter = conn.do_update(editor.revnum, "", True, editor)
 
                             try:
                                 # Report status of existing paths
                                 reporter.set_path("", parent_revnum, False, None)
                             except:
-                                reporter.abort_report()
+                                reporter.abort()
                                 raise
 
-                        reporter.finish_report()
+                        reporter.finish()
                     finally:
                         if conn is not None:
                             self.source.transport.add_connection(conn)
                 except:
-                    editor.abort_edit()
+                    editor.abort()
                     raise
 
                 prev_inv = editor.inventory
@@ -687,6 +689,8 @@ class InterFromSvnRepository(InterRepository):
         """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:
index 481953f5d8254ffb0d7e77de74e88fe31d91f489..b745380c8bc11f8b2af1d55b52e102a04131db15 100644 (file)
--- a/format.py
+++ b/format.py
@@ -23,8 +23,7 @@ from bzrlib.lockable_files import TransportLock
 import os
 
 lazy_import(globals(), """
-from bzrlib.plugins.svn import errors
-from bzrlib.plugins.svn import remote
+from bzrlib.plugins.svn import errors, remote
 
 from bzrlib import errors as bzr_errors
 """)
@@ -52,12 +51,12 @@ class SvnRemoteFormat(BzrDirFormat):
     @classmethod
     def probe_transport(klass, transport):
         from transport import get_svn_ra_transport
-        import svn.core
+        from bzrlib.plugins.svn.core import SubversionException
         format = klass()
 
         try:
             transport = get_svn_ra_transport(transport)
-        except svn.core.SubversionException, (_, num):
+        except SubversionException, (_, num):
             if num in (errors.ERR_RA_ILLEGAL_URL, \
                        errors.ERR_RA_LOCAL_REPOS_OPEN_FAILED, \
                        errors.ERR_BAD_URL):
@@ -66,10 +65,10 @@ class SvnRemoteFormat(BzrDirFormat):
         return format
 
     def _open(self, transport):
-        import svn.core
+        from bzrlib.plugins.svn.core import SubversionException
         try: 
             return remote.SvnRemoteAccess(transport, self)
-        except svn.core.SubversionException, (_, num):
+        except SubversionException, (_, num):
             if num == errors.ERR_RA_DAV_REQUEST_FAILED:
                 raise bzr_errors.NotBranchError(transport.base)
             raise
@@ -84,7 +83,7 @@ class SvnRemoteFormat(BzrDirFormat):
         """See BzrDir.initialize_on_transport()."""
         from transport import get_svn_ra_transport
         from bzrlib.transport.local import LocalTransport
-        import svn.repos
+        import repos
 
         if not isinstance(transport, LocalTransport):
             raise NotImplementedError(self.initialize, 
@@ -92,7 +91,7 @@ class SvnRemoteFormat(BzrDirFormat):
                 "non-local transports")
 
         local_path = transport._local_base.rstrip("/")
-        svn.repos.create(local_path, '', '', None, None)
+        repos.create(local_path)
         # All revision property changes
         revprop_hook = os.path.join(local_path, "hooks", "pre-revprop-change")
         open(revprop_hook, 'w').write("#!/bin/sh")
@@ -115,25 +114,22 @@ class SvnWorkingTreeDirFormat(BzrDirFormat):
 
     @classmethod
     def probe_transport(klass, transport):
-        import svn
         from bzrlib.transport.local import LocalTransport
+        import wc
         format = klass()
 
         if isinstance(transport, LocalTransport) and \
-            transport.has(svn.wc.get_adm_dir()):
+            transport.has(wc.get_adm_dir()):
             return format
 
         raise bzr_errors.NotBranchError(path=transport.base)
 
     def _open(self, transport):
-        import svn.core
+        from bzrlib.plugins.svn.core import SubversionException
         from workingtree import SvnCheckout
-        subr_version = svn.core.svn_subr_version()
-        if subr_version.major == 1 and subr_version.minor < 4:
-            raise errors.NoCheckoutSupport()
         try:
             return SvnCheckout(transport, self)
-        except svn.core.SubversionException, (_, num):
+        except SubversionException, (_, num):
             if num in (errors.ERR_RA_LOCAL_REPOS_OPEN_FAILED,):
                 raise errors.NoSvnRepositoryPresent(transport.base)
             raise
index 30ee898e13b988794e39ed1bc8f323c0ea3fdf1f..423e0d2e6aef9244d5986a5f15f31373a5507827 100644 (file)
@@ -20,15 +20,14 @@ from bzrlib.errors import NoSuchRevision
 from bzrlib.trace import mutter
 import bzrlib.ui as ui
 
-from svn.core import SubversionException, Pool
-from transport import SvnRaTransport
-import svn.core
+from bzrlib.plugins.svn.core import SubversionException
+from bzrlib.plugins.svn.transport import SvnRaTransport
+from bzrlib.plugins.svn import core
 
 from bzrlib.plugins.svn.cache import CacheTable
-from bzrlib.plugins.svn import changes
 from bzrlib.plugins.svn.errors import ERR_FS_NO_SUCH_REVISION, ERR_FS_NOT_FOUND
-
-LOG_CHUNK_LIMIT = 0
+from bzrlib.plugins.svn import changes
+from bzrlib.plugins.svn.ra import DIRENT_KIND
 
 class lazy_dict(object):
     def __init__(self, initial, create_fn, *args):
@@ -189,6 +188,7 @@ class CachingLogWalker(CacheTable):
             revprops = lazy_dict({}, self._transport.revprop_list, revnum)
 
             if changes.changes_path(revpaths, path, True):
+                assert isinstance(revnum, int)
                 yield (revpaths, revnum, revprops)
                 i += 1
                 if limit != 0 and i == limit:
@@ -197,7 +197,11 @@ class CachingLogWalker(CacheTable):
             if next is None:
                 break
 
+            assert (ascending and next[1] > revnum) or \
+                   (not ascending and next[1] < revnum)
             (path, revnum) = next
+            assert isinstance(path, str)
+            assert isinstance(revnum, int)
 
     def get_previous(self, path, revnum):
         """Return path,revnum pair specified pair was derived from.
@@ -251,36 +255,40 @@ class CachingLogWalker(CacheTable):
 
         :param to_revnum: End of range to fetch information for
         """
+        assert isinstance(self.saved_revnum, int)
         if to_revnum <= self.saved_revnum:
             return
         latest_revnum = self.actual._transport.get_latest_revnum()
+        assert isinstance(latest_revnum, int)
         to_revnum = max(latest_revnum, to_revnum)
 
         pb = ui.ui_factory.nested_progress_bar()
 
         try:
+            def update_db(orig_paths, revision, revprops):
+                assert isinstance(orig_paths, dict) or orig_paths is None
+                assert isinstance(revision, int)
+                assert isinstance(revprops, dict)
+                pb.update('fetching svn revision info', revision, to_revnum)
+                if orig_paths is None:
+                    orig_paths = {}
+                for p in orig_paths:
+                    (action, copyfrom_path, copyfrom_rev) = orig_paths[p]
+
+                    if copyfrom_path:
+                        copyfrom_path = copyfrom_path.strip("/")
+                    self.cachedb.execute(
+                         "replace into changed_path (rev, path, action, copyfrom_path, copyfrom_rev) values (?, ?, ?, ?, ?)", 
+                         (revision, p.strip("/"), action, copyfrom_path, copyfrom_rev))
+
+                self.saved_revnum = revision
+                if self.saved_revnum % 1000 == 0:
+                    self.cachedb.commit()
+
             try:
-                while self.saved_revnum < to_revnum:
-                    for (orig_paths, revision, revprops) in self.actual._transport.iter_log(None, self.saved_revnum, 
-                                             to_revnum, self.actual._limit, True, 
-                                             True, []):
-                        pb.update('fetching svn revision info', revision, to_revnum)
-                        if orig_paths is None:
-                            orig_paths = {}
-                        for p in orig_paths:
-                            copyfrom_path = orig_paths[p].copyfrom_path
-                            if copyfrom_path is not None:
-                                copyfrom_path = copyfrom_path.strip("/")
-
-                            self.cachedb.execute(
-                                 "replace into changed_path (rev, path, action, copyfrom_path, copyfrom_rev) values (?, ?, ?, ?, ?)", 
-                                 (revision, p.strip("/"), orig_paths[p].action, copyfrom_path, orig_paths[p].copyfrom_rev))
-                            # Work around nasty memory leak in Subversion
-                            orig_paths[p]._parent_pool.destroy()
-
-                        self.saved_revnum = revision
-                        if self.saved_revnum % 1000 == 0:
-                            self.cachedb.commit()
+                assert isinstance(self.saved_revnum, int)
+                assert isinstance(to_revnum, int)
+                self.actual._transport.get_log(update_db, None, self.saved_revnum, to_revnum, 0, True, True, [])
             finally:
                 pb.finished()
         except SubversionException, (_, num):
@@ -294,12 +302,12 @@ class CachingLogWalker(CacheTable):
 def struct_revpaths_to_tuples(changed_paths):
     assert isinstance(changed_paths, dict)
     revpaths = {}
-    for k,v in changed_paths.items():
-        if v.copyfrom_path is None:
+    for k,(action, copyfrom_path, copyfrom_rev) in changed_paths.items():
+        if copyfrom_path is None:
             copyfrom_path = None
         else:
-            copyfrom_path = v.copyfrom_path.strip("/")
-        revpaths[k.strip("/")] = (v.action, copyfrom_path, v.copyfrom_rev)
+            copyfrom_path = copyfrom_path.strip("/")
+        revpaths[k.strip("/")] = (action, copyfrom_path, copyfrom_rev)
     return revpaths
 
 
@@ -314,11 +322,6 @@ class LogWalker(object):
 
         self._transport = transport
 
-        if limit is not None:
-            self._limit = limit
-        else:
-            self._limit = LOG_CHUNK_LIMIT
-
     def find_latest_change(self, path, revnum):
         """Find latest revision that touched path.
 
@@ -395,67 +398,20 @@ class LogWalker(object):
         assert isinstance(path, str), "invalid path"
         path = path.strip("/")
         conn = self._transport.connections.get(self._transport.get_svn_repos_root())
+        results = []
+        unchecked_dirs = set([path])
         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 set_target_revision(self, revnum):
-                """See Editor.set_target_revision()."""
-                pass
-
-            def open_root(self, revnum, baton):
-                """See Editor.open_root()."""
-                return 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 path
-
-            def change_dir_prop(self, id, name, value, pool):
-                pass
-
-            def change_file_prop(self, id, name, value, pool):
-                pass
-
-            def add_file(self, path, parent_id, copyfrom_path, copyfrom_revnum, baton):
-                self.files.append(urlutils.join(self.base, path))
-                return path
-
-            def close_dir(self, id):
-                pass
-
-            def close_file(self, path, checksum):
-                pass
-
-            def close_edit(self):
-                pass
-
-            def abort_edit(self):
-                pass
-
-            def apply_textdelta(self, file_id, base_checksum):
-                pass
-
-        pool = Pool()
-        editor = TreeLister(path)
-        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)
+            while len(unchecked_dirs) > 0:
+                nextp = unchecked_dirs.pop()
+                dirents = conn.get_dir(nextp, revnum, DIRENT_KIND)[0]
+                for k,v in dirents.items():
+                    childp = urlutils.join(nextp, k)
+                    if v['kind'] == core.NODE_DIR:
+                        unchecked_dirs.add(childp)
+                    results.append(childp)
         finally:
             self._transport.connections.add(conn)
-        return editor.files
+        return results
 
     def get_previous(self, path, revnum):
         """Return path,revnum pair specified pair was derived from.
index d0fffc8c0c9fa8dea8bbf14ee02b9114d21f2c08..e201b9de26a4e2fea192726a9ee34a25a3c298ed 100644 (file)
@@ -19,9 +19,9 @@ from bzrlib import osutils, registry
 from bzrlib.errors import InvalidRevisionId
 from bzrlib.trace import mutter
 
-from bzrlib.plugins.svn import version_info, errors, properties
+from bzrlib.plugins.svn import core, constants, version_info, errors, properties
 import calendar
-import svn
+import sha
 import time
 import urllib
 
@@ -151,7 +151,7 @@ def parse_svn_revprops(svn_revprops, rev):
             pass
 
     if svn_revprops.has_key(properties.PROP_REVISION_DATE):
-        rev.timestamp = 1.0 * svn.core.secs_from_timestr(svn_revprops[properties.PROP_REVISION_DATE], None)
+        rev.timestamp = core.time_from_cstring(svn_revprops[properties.PROP_REVISION_DATE]) / 1000000.0
     else:
         rev.timestamp = 0.0 # FIXME: Obtain repository creation time
     rev.timezone = None
index 28ad66705b20e11b92a30b7ba2c3464b853b48ff..d59d38620c1910d87e106a12171fc676d81db981 100644 (file)
 from bzrlib import osutils, ui
 from bzrlib.errors import InvalidRevisionId
 from bzrlib.trace import mutter
-from bzrlib.plugins.svn import mapping, properties
+
+from bzrlib.plugins.svn import core, mapping, properties
+from bzrlib.plugins.svn.core import SubversionException
 from bzrlib.plugins.svn.errors import ERR_FS_NOT_DIRECTORY, ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND
 from bzrlib.plugins.svn.layout import RepositoryLayout
 from bzrlib.plugins.svn.mapping3.scheme import (BranchingScheme, guess_scheme_from_branch_path, 
                              guess_scheme_from_history, ListBranchingScheme, 
                              parse_list_scheme_text, NoBranchingScheme,
                              TrunkBranchingScheme, ListBranchingScheme)
-import sha, svn
-from svn.core import SubversionException
+import sha
 
 SVN_PROP_BZR_BRANCHING_SCHEME = 'bzr:branching-scheme'
 
@@ -67,9 +68,10 @@ class SchemeDerivedLayout(RepositoryLayout):
 
     def get_branches(self, revnum, project=""):
         def check_path(path):
-            return self.repository.transport.check_path(path, revnum) == svn.core.svn_node_dir
+            return self.repository.transport.check_path(path, revnum) == core.NODE_DIR
         def find_children(path):
             try:
+                assert not path.startswith("/")
                 dirents = self.repository.transport.get_dir(path, revnum)[0]
             except SubversionException, (msg, num):
                 if num in (ERR_FS_NOT_DIRECTORY, ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND):
@@ -117,15 +119,12 @@ def get_property_scheme(repository, revnum=None):
 
 
 def set_property_scheme(repository, scheme):
-    def done(revmetadata, pool):
-        pass
     editor = repository.transport.get_commit_editor(
-            {properties.PROP_REVISION_LOG: "Updating branching scheme for Bazaar."},
-            done, None, False)
-    root = editor.open_root(-1)
-    editor.change_dir_prop(root, SVN_PROP_BZR_BRANCHING_SCHEME, 
+            {properties.PROP_REVISION_LOG: "Updating branching scheme for Bazaar."})
+    root = editor.open_root()
+    root.change_prop(SVN_PROP_BZR_BRANCHING_SCHEME, 
             "".join(map(lambda x: x+"\n", scheme.branch_list)).encode("utf-8"))
-    editor.close_directory(root)
+    root.close()
     editor.close()
 
 
diff --git a/ra.c b/ra.c
new file mode 100644 (file)
index 0000000..3e2c6a2
--- /dev/null
+++ b/ra.c
@@ -0,0 +1,1669 @@
+/* Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <stdbool.h>
+#include <Python.h>
+#include <apr_general.h>
+#include <svn_types.h>
+#include <svn_ra.h>
+#include <apr_file_io.h>
+
+#include <structmember.h>
+
+#include "editor.h"
+#include "util.h"
+
+static PyObject *busy_exc;
+
+PyAPI_DATA(PyTypeObject) Reporter_Type;
+PyAPI_DATA(PyTypeObject) RemoteAccess_Type;
+PyAPI_DATA(PyTypeObject) Auth_Type;
+PyAPI_DATA(PyTypeObject) AuthProvider_Type;
+PyAPI_DATA(PyTypeObject) TxDeltaWindowHandler_Type;
+
+typedef struct {
+       PyObject_HEAD
+    svn_auth_baton_t *auth_baton;
+    apr_pool_t *pool;
+    PyObject *providers;
+} AuthObject;
+
+static svn_error_t *py_commit_callback(const svn_commit_info_t *commit_info, void *baton, apr_pool_t *pool)
+{
+       PyObject *fn = (PyObject *)baton, *ret;
+
+       if (fn == Py_None)
+               return NULL;
+
+       ret = PyObject_CallFunction(fn, "izz", 
+                                                               commit_info->revision, commit_info->date, 
+                                                               commit_info->author);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+       return NULL;
+}
+
+static PyObject *pyify_lock(const svn_lock_t *lock)
+{
+    Py_RETURN_NONE; /* FIXME */
+}
+
+static svn_error_t *py_lock_func (void *baton, const char *path, int do_lock, 
+                           const svn_lock_t *lock, svn_error_t *ra_err, 
+                           apr_pool_t *pool)
+{
+    PyObject *py_ra_err = Py_None, *ret, *py_lock;
+    if (ra_err != NULL) {
+        py_ra_err = PyErr_NewSubversionException(ra_err);
+       }
+       py_lock = pyify_lock(lock);
+    ret = PyObject_CallFunction((PyObject *)baton, "zbOO", path, do_lock, 
+                                                 py_lock, py_ra_err);
+       Py_DECREF(py_lock);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+       return NULL;
+}
+
+static void py_progress_func(apr_off_t progress, apr_off_t total, void *baton, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)baton, *ret;
+    if (fn == Py_None) {
+        return;
+       }
+       ret = PyObject_CallFunction(fn, "ll", progress, total);
+       /* TODO: What to do with exceptions raised here ? */
+       if (ret == NULL)
+               return;
+       Py_DECREF(ret);
+}
+
+
+typedef struct {
+       PyObject_HEAD
+    const svn_ra_reporter2_t *reporter;
+    void *report_baton;
+    apr_pool_t *pool;
+       void (*done_cb)(void *baton);
+       void *done_baton;
+} ReporterObject;
+
+static PyObject *reporter_set_path(PyObject *self, PyObject *args)
+{
+       char *path; 
+       svn_revnum_t revision; 
+       bool start_empty; 
+       char *lock_token = NULL;
+       ReporterObject *reporter = (ReporterObject *)self;
+
+       if (!PyArg_ParseTuple(args, "slb|z", &path, &revision, &start_empty, 
+                                                 &lock_token))
+               return NULL;
+
+    if (!check_error(reporter->reporter->set_path(reporter->report_baton, 
+                                                                                                 path, revision, start_empty, 
+                                        lock_token, reporter->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *reporter_delete_path(PyObject *self, PyObject *args)
+{
+       ReporterObject *reporter = (ReporterObject *)self;
+       char *path;
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+       if (!check_error(reporter->reporter->delete_path(reporter->report_baton, 
+                                                                                                       path, reporter->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *reporter_link_path(PyObject *self, PyObject *args)
+{
+       char *path, *url;
+       svn_revnum_t revision;
+       bool start_empty;
+       char *lock_token = NULL;
+       ReporterObject *reporter = (ReporterObject *)self;
+
+       if (!PyArg_ParseTuple(args, "sslb|z", &path, &url, &revision, &start_empty, &lock_token))
+               return NULL;
+
+       if (!check_error(reporter->reporter->link_path(reporter->report_baton, path, url, 
+                               revision, start_empty, lock_token, reporter->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *reporter_finish(PyObject *self)
+{
+       ReporterObject *reporter = (ReporterObject *)self;
+
+       if (reporter->done_cb != NULL)
+               reporter->done_cb(reporter->done_baton);
+
+       if (!check_error(reporter->reporter->finish_report(reporter->report_baton, 
+                                                                                                         reporter->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *reporter_abort(PyObject *self)
+{
+       ReporterObject *reporter = (ReporterObject *)self;
+       
+       if (reporter->done_cb != NULL)
+               reporter->done_cb(reporter->done_baton);
+
+       if (!check_error(reporter->reporter->abort_report(reporter->report_baton, 
+                                                                                                        reporter->pool)))
+               return NULL;
+
+       Py_RETURN_NONE;
+}
+
+static PyMethodDef reporter_methods[] = {
+       { "abort", (PyCFunction)reporter_abort, METH_NOARGS, NULL },
+       { "finish", (PyCFunction)reporter_finish, METH_NOARGS, NULL },
+       { "link_path", (PyCFunction)reporter_link_path, METH_VARARGS, NULL },
+       { "set_path", (PyCFunction)reporter_set_path, METH_VARARGS, NULL },
+       { "delete_path", (PyCFunction)reporter_delete_path, METH_VARARGS, NULL },
+       { NULL, }
+};
+
+static void reporter_dealloc(PyObject *self)
+{
+       ReporterObject *reporter = (ReporterObject *)self;
+       /* FIXME: Warn the user if abort_report/finish_report wasn't called? */
+       apr_pool_destroy(reporter->pool);
+       PyObject_Del(self);
+}
+
+PyTypeObject Reporter_Type = {
+       PyObject_HEAD_INIT(NULL) 0,
+       .tp_name = "ra.Reporter",
+       .tp_basicsize = sizeof(ReporterObject),
+       .tp_methods = reporter_methods,
+       .tp_dealloc = reporter_dealloc,
+};
+
+/**
+ * Get libsvn_ra version information.
+ *
+ * :return: tuple with major, minor, patch version number and tag.
+ */
+static PyObject *version(PyObject *self)
+{
+    const svn_version_t *ver = svn_ra_version();
+    return Py_BuildValue("(iiis)", ver->major, ver->minor, 
+                                                ver->patch, ver->tag);
+}
+
+static svn_error_t *py_cb_editor_set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)edit_baton, *ret;
+
+       ret = PyObject_CallMethod(self, "set_target_revision", "l", target_revision);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton)
+{
+    PyObject *self = (PyObject *)edit_baton, *ret;
+    *root_baton = NULL;
+    ret = PyObject_CallMethod(self, "open_root", "l", base_revision);
+       if (ret == NULL)
+               return py_svn_error();
+    *root_baton = (void *)ret;
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_delete_entry(const char *path, long revision, void *parent_baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+       ret = PyObject_CallMethod(self, "delete_entry", "sl", path, revision);
+       if (ret == NULL)
+               return py_svn_error();
+    Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_add_directory(const char *path, void *parent_baton, const char *copyfrom_path, long copyfrom_revision, apr_pool_t *pool, void **child_baton)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+    *child_baton = NULL;
+    if (copyfrom_path == NULL) {
+        ret = PyObject_CallMethod(self, "add_directory", "s", path);
+       } else {
+        ret = PyObject_CallMethod(self, "add_directory", "ssl", path, copyfrom_path, copyfrom_revision);
+       }
+       if (ret == NULL)
+               return py_svn_error();
+    *child_baton = (void *)ret;
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_open_directory(const char *path, void *parent_baton, long base_revision, apr_pool_t *pool, void **child_baton)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+    *child_baton = NULL;
+    ret = PyObject_CallMethod(self, "open_directory", "sl", path, base_revision);
+       if (ret == NULL)
+               return py_svn_error();
+    *child_baton = (void *)ret;
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_change_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)dir_baton, *ret;
+       if (value != NULL) {
+               ret = PyObject_CallMethod(self, "change_prop", "sz#", name, value->data, value->len);
+       } else {
+               ret = PyObject_CallMethod(self, "change_prop", "sO", name, Py_None);
+       }
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_close_directory(void *dir_baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)dir_baton, *ret;
+    ret = PyObject_CallMethod(self, "close", "");
+    Py_DECREF(self);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+       ret = PyObject_CallMethod(self, "absent_directory", "s", path);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_add_file(const char *path, void *parent_baton, const char *copy_path, long copy_revision, apr_pool_t *file_pool, void **file_baton)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+    if (copy_path == NULL) {
+               ret = PyObject_CallMethod(self, "add_file", "s", path);
+       } else {
+               ret = PyObject_CallMethod(self, "add_file", "ssl", path, copy_path, 
+                                                                 copy_revision);
+       }
+       if (ret == NULL)
+               return py_svn_error();
+    *file_baton = (void *)ret;
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_open_file(const char *path, void *parent_baton, long base_revision, apr_pool_t *file_pool, void **file_baton)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+    ret = PyObject_CallMethod(self, "open_file", "sl", path, base_revision);
+       if (ret == NULL)
+               return py_svn_error();
+    *file_baton = (void *)ret;
+    return NULL;
+}
+
+static svn_error_t *py_txdelta_window_handler(svn_txdelta_window_t *window, void *baton)
+{
+       int i;
+       PyObject *ops, *ret;
+    PyObject *fn = (PyObject *)baton, *py_new_data, *py_window;
+    if (window == NULL) {
+        /* Signals all delta windows have been received */
+        Py_DECREF(fn);
+        return NULL;
+       }
+    if (fn == Py_None) {
+        /* User doesn't care about deltas */
+        return NULL;
+       }
+    ops = PyList_New(window->num_ops);
+       for (i = 0; i < window->num_ops; i++) {
+               PyList_SetItem(ops, i, Py_BuildValue("(iII)", window->ops[i].action_code, 
+                                       window->ops[i].offset, 
+                                       window->ops[i].length));
+       }
+       if (window->new_data != NULL && window->new_data->data != NULL) {
+               py_new_data = PyString_FromStringAndSize(window->new_data->data, window->new_data->len);
+       } else {
+               py_new_data = Py_None;
+       }
+       py_window = Py_BuildValue("((LIIiOO))", window->sview_offset, window->sview_len, window->tview_len, 
+                                                               window->src_ops, ops, py_new_data);
+       ret = PyObject_CallFunction(fn, "O", py_window);
+       Py_DECREF(py_window);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton)
+{
+    PyObject *self = (PyObject *)file_baton, *ret;
+    *handler_baton = NULL;
+       ret = PyObject_CallMethod(self, "apply_textdelta", "z", base_checksum);
+       if (ret == NULL)
+               return py_svn_error();
+    *handler_baton = (void *)ret;
+    *handler = py_txdelta_window_handler;
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_close_file(void *file_baton, 
+                                                                                const char *text_checksum, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)file_baton, *ret;
+    if (text_checksum != NULL) {
+               ret = PyObject_CallMethod(self, "close", "");
+       } else {
+               ret = PyObject_CallMethod(self, "close", "s", text_checksum);
+       }
+    Py_DECREF(self);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_absent_file(const char *path, void *parent_baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)parent_baton, *ret;
+       ret = PyObject_CallMethod(self, "absent_file", "s", path);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_close_edit(void *edit_baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)edit_baton, *ret;
+       ret = PyObject_CallMethod(self, "close", "");
+       Py_DECREF(self);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static svn_error_t *py_cb_editor_abort_edit(void *edit_baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)edit_baton, *ret;
+       ret = PyObject_CallMethod(self, "abort", "");
+       Py_DECREF(self);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+static const svn_delta_editor_t py_editor = {
+       .set_target_revision = py_cb_editor_set_target_revision,
+       .open_root = py_cb_editor_open_root,
+       .delete_entry = py_cb_editor_delete_entry,
+       .add_directory = py_cb_editor_add_directory,
+       .open_directory = py_cb_editor_open_directory,
+       .change_dir_prop = py_cb_editor_change_prop,
+       .close_directory = py_cb_editor_close_directory,
+       .absent_directory = py_cb_editor_absent_directory,
+       .add_file = py_cb_editor_add_file,
+       .open_file = py_cb_editor_open_file,
+       .apply_textdelta = py_cb_editor_apply_textdelta,
+       .change_file_prop = py_cb_editor_change_prop,
+       .close_file = py_cb_editor_close_file,
+       .absent_file = py_cb_editor_absent_file,
+       .close_edit = py_cb_editor_close_edit,
+       .abort_edit = py_cb_editor_abort_edit
+};
+
+static svn_error_t *py_file_rev_handler(void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, svn_txdelta_window_handler_t *delta_handler, void **delta_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)baton, *ret, *py_rev_props;
+
+       py_rev_props = prop_hash_to_dict(rev_props);
+       if (py_rev_props == NULL)
+               return py_svn_error();
+
+       /* FIXME: delta handler */
+       ret = PyObject_CallFunction(fn, "slO", path, rev, py_rev_props);
+       Py_DECREF(py_rev_props);
+       if (ret == NULL)
+               return py_svn_error();
+       Py_DECREF(ret);
+    return NULL;
+}
+
+/** Connection to a remote Subversion repository. */
+typedef struct {
+       PyObject_HEAD
+       svn_ra_session_t *ra;
+    apr_pool_t *pool;
+    char *url;
+    PyObject *progress_func;
+       AuthObject *auth;
+       bool busy;
+} RemoteAccessObject;
+
+static void ra_done_handler(void *_ra)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)_ra;
+
+       ra->busy = false;
+}
+
+#define RUN_RA_WITH_POOL(pool, ra, cmd)  \
+       if (!check_error((cmd))) { \
+               apr_pool_destroy(pool); \
+               ra->busy = false; \
+               return NULL; \
+       } \
+       ra->busy = false;
+
+static bool ra_check_busy(RemoteAccessObject *raobj)
+{
+       if (raobj->busy) {
+               PyErr_SetString(busy_exc, "Remote access object already in use");
+               return true;
+       }
+       raobj->busy = true;
+       return false;
+}
+
+static svn_error_t *py_open_tmp_file(apr_file_t **fp, void *callback,
+                                                                        apr_pool_t *pool)
+{
+       RemoteAccessObject *self = (RemoteAccessObject *)callback;
+
+       PyErr_SetString(PyExc_NotImplementedError, "open_tmp_file not wrapped yet");
+       
+       return py_svn_error(); /* FIXME */
+}
+
+static PyObject *ra_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+       char *kwnames[] = { "url", "progress_cb", "auth", "config", NULL };
+       char *url;
+       PyObject *progress_cb = Py_None;
+       AuthObject *auth = (AuthObject *)Py_None;
+       PyObject *config = Py_None;
+       RemoteAccessObject *ret;
+       apr_hash_t *config_hash;
+       svn_ra_callbacks2_t *callbacks2;
+       Py_ssize_t idx = 0;
+       PyObject *key, *value;
+       svn_auth_baton_t *auth_baton;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOO", kwnames, &url, &progress_cb, (PyObject **)&auth, &config))
+               return NULL;
+
+       ret = PyObject_New(RemoteAccessObject, &RemoteAccess_Type);
+       if (ret == NULL)
+               return NULL;
+
+       if ((PyObject *)auth == Py_None) {
+               auth_baton = NULL;
+               ret->auth = NULL;
+       } else {
+               /* FIXME: check auth is an instance of Auth_Type */
+               Py_INCREF(auth);
+               ret->auth = auth;
+               auth_baton = ret->auth->auth_baton;
+       }
+
+    ret->pool = Pool();
+       if (ret->pool == NULL)
+               return NULL;
+       ret->url = apr_pstrdup(ret->pool, url);
+       if (!check_error(svn_ra_create_callbacks(&callbacks2, ret->pool))) {
+               apr_pool_destroy(ret->pool);
+               PyObject_Del(ret);
+               return NULL;
+       }
+
+       callbacks2->progress_func = py_progress_func;
+       callbacks2->auth_baton = auth_baton;
+       callbacks2->open_tmp_file = py_open_tmp_file;
+       ret->progress_func = progress_cb;
+       callbacks2->progress_baton = (void *)ret->progress_func;
+       config_hash = apr_hash_make(ret->pool);
+       if (PyDict_Check(config)) {
+               while (PyDict_Next(config, &idx, &key, &value)) {
+                       apr_hash_set(config_hash, apr_pstrdup(ret->pool, PyString_AsString(key)), 
+                                                PyString_Size(key), apr_pstrdup(ret->pool, PyString_AsString(value)));
+               }
+       } else if (config != Py_None) {
+               PyErr_SetString(PyExc_TypeError, "Expected dictionary for config");
+               apr_pool_destroy(ret->pool);
+               PyObject_Del(ret);
+               return NULL;
+       }
+       if (!check_error(svn_ra_open2(&ret->ra, apr_pstrdup(ret->pool, url), 
+                                                                 callbacks2, ret, config_hash, ret->pool))) {
+               apr_pool_destroy(ret->pool);
+               PyObject_Del(ret);
+               return NULL;
+       }
+       ret->busy = false;
+       return (PyObject *)ret;
+}
+
+ /**
+  * Obtain the globally unique identifier for this repository.
+  */
+static PyObject *ra_get_uuid(PyObject *self)
+{
+       const char *uuid;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       PyObject *ret;
+    apr_pool_t *temp_pool;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_uuid(ra->ra, &uuid, temp_pool));
+       ret = PyString_FromString(uuid);
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+
+/** Switch to a different url. */
+static PyObject *ra_reparent(PyObject *self, PyObject *args)
+{
+       char *url;
+       apr_pool_t *temp_pool;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+
+       if (!PyArg_ParseTuple(args, "s", &url))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_reparent(ra->ra, url, temp_pool));
+       apr_pool_destroy(temp_pool);
+       Py_RETURN_NONE;
+}
+
+/**
+ * Obtain the number of the latest committed revision in the 
+ * connected repository.
+ */
+static PyObject *ra_get_latest_revnum(PyObject *self)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       svn_revnum_t latest_revnum;
+    apr_pool_t *temp_pool;
+       if (ra_check_busy(ra))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra,
+                                 svn_ra_get_latest_revnum(ra->ra, &latest_revnum, temp_pool));
+    apr_pool_destroy(temp_pool);
+    return PyInt_FromLong(latest_revnum);
+}
+
+static PyObject *ra_get_log(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+       char *kwnames[] = { "callback", "paths", "start", "end", "limit",
+               "discover_changed_paths", "strict_node_history", "revprops", NULL };
+       PyObject *callback, *paths;
+       svn_revnum_t start = 0, end = 0;
+       int limit=0; 
+       bool discover_changed_paths=false, strict_node_history=true;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       PyObject *revprops = Py_None;
+    apr_pool_t *temp_pool;
+       apr_array_header_t *apr_paths;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOll|ibbO:get_log", kwnames, 
+                                                &callback, &paths, &start, &end, &limit,
+                                                &discover_changed_paths, &strict_node_history,
+                                                &revprops))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (paths == Py_None) {
+               /* FIXME: The subversion libraries don't behave as expected, 
+                * so tweak our own parameters a bit. */
+               apr_paths = apr_array_make(temp_pool, 1, sizeof(char *));
+               APR_ARRAY_PUSH(apr_paths, char *) = apr_pstrdup(temp_pool, "");
+       } else if (!string_list_to_apr_array(temp_pool, paths, &apr_paths)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_log(ra->ra, 
+            apr_paths, start, end, limit,
+            discover_changed_paths, strict_node_history, py_svn_log_wrapper, 
+            callback, temp_pool));
+    apr_pool_destroy(temp_pool);
+       Py_RETURN_NONE;
+}
+
+/**
+ * Obtain the URL of the root of this repository.
+ */
+static PyObject *ra_get_repos_root(PyObject *self)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       const char *root;
+    apr_pool_t *temp_pool;
+       
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra,
+                                         svn_ra_get_repos_root(ra->ra, &root, temp_pool));
+       apr_pool_destroy(temp_pool);
+       return PyString_FromString(root);
+}
+
+static PyObject *ra_do_update(PyObject *self, PyObject *args)
+{
+       svn_revnum_t revision_to_update_to;
+       char *update_target; 
+       bool recurse;
+       PyObject *update_editor;
+    const svn_ra_reporter2_t *reporter;
+       void *report_baton;
+       apr_pool_t *temp_pool;
+       ReporterObject *ret;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+
+       if (!PyArg_ParseTuple(args, "lsbO:do_update", &revision_to_update_to, &update_target, &recurse, &update_editor))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+
+       Py_INCREF(update_editor);
+       if (!check_error(svn_ra_do_update(ra->ra, &reporter, 
+                                                                                                 &report_baton, 
+                                                                                                 revision_to_update_to, 
+                                                                                                 update_target, recurse, 
+                                                                                                 &py_editor, update_editor, 
+                                                                                                 temp_pool))) {
+               apr_pool_destroy(temp_pool);
+               ra->busy = false;
+               return NULL;
+       }
+       ret = PyObject_New(ReporterObject, &Reporter_Type);
+       if (ret == NULL)
+               return NULL;
+       ret->reporter = reporter;
+       ret->report_baton = report_baton;
+       ret->pool = temp_pool;
+       ret->done_cb = ra_done_handler;
+       ret->done_baton = ra;
+       return (PyObject *)ret;
+}
+
+static PyObject *ra_do_switch(PyObject *self, PyObject *args)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       svn_revnum_t revision_to_update_to;
+       char *update_target; 
+       bool recurse;
+       char *switch_url; 
+       PyObject *update_editor;
+       const svn_ra_reporter2_t *reporter;
+       void *report_baton;
+       apr_pool_t *temp_pool;
+       ReporterObject *ret;
+
+       if (!PyArg_ParseTuple(args, "lsbsO:do_switch", &revision_to_update_to, &update_target, 
+                                                 &recurse, &switch_url, &update_editor))
+               return NULL;
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       Py_INCREF(update_editor);
+       if (!check_error(svn_ra_do_switch(
+                                               ra->ra, &reporter, &report_baton, 
+                                               revision_to_update_to, update_target, 
+                                               recurse, switch_url, &py_editor, 
+                                               update_editor, temp_pool))) {
+               apr_pool_destroy(temp_pool);
+               ra->busy = false;
+               return NULL;
+       }
+       ret = PyObject_New(ReporterObject, &Reporter_Type);
+       if (ret == NULL)
+               return NULL;
+       ret->reporter = reporter;
+       ret->report_baton = report_baton;
+       ret->pool = temp_pool;
+       ret->done_cb = ra_done_handler;
+       ret->done_baton = ra;
+       return (PyObject *)ret;
+}
+
+static PyObject *ra_replay(PyObject *self, PyObject *args)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       apr_pool_t *temp_pool;
+       svn_revnum_t revision, low_water_mark;
+       PyObject *update_editor;
+       bool send_deltas = true;
+
+       if (!PyArg_ParseTuple(args, "llO|b", &revision, &low_water_mark, &update_editor, &send_deltas))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       Py_INCREF(update_editor);
+    RUN_RA_WITH_POOL(temp_pool, ra,
+                                         svn_ra_replay(ra->ra, revision, low_water_mark,
+                                                                       send_deltas, &py_editor, update_editor, 
+                                                                       temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *ra_rev_proplist(PyObject *self, PyObject *args)
+{
+       apr_pool_t *temp_pool;
+       apr_hash_t *props;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       svn_revnum_t rev;
+       PyObject *py_props;
+       if (!PyArg_ParseTuple(args, "l", &rev))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra,
+                                         svn_ra_rev_proplist(ra->ra, rev, &props, temp_pool));
+       py_props = prop_hash_to_dict(props);
+       apr_pool_destroy(temp_pool);
+       return py_props;
+}
+
+static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+       char *kwnames[] = { "revprops", "callback", "lock_tokens", "keep_locks", 
+               NULL };
+       PyObject *revprops, *commit_callback = Py_None, *lock_tokens = Py_None;
+       bool keep_locks = false;
+       apr_pool_t *temp_pool, *pool;
+       const svn_delta_editor_t *editor;
+       void *edit_baton;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       apr_hash_t *hash_lock_tokens;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOb", kwnames, &revprops, &commit_callback, &lock_tokens, &keep_locks))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (lock_tokens == Py_None) {
+               hash_lock_tokens = NULL;
+       } else {
+               Py_ssize_t idx = 0;
+               PyObject *k, *v;
+               hash_lock_tokens = apr_hash_make(temp_pool);
+               while (PyDict_Next(lock_tokens, &idx, &k, &v)) {
+                       apr_hash_set(hash_lock_tokens, PyString_AsString(k), 
+                                                PyString_Size(k), PyString_AsString(v));
+               }
+       }
+
+       if (!PyDict_Check(revprops)) {
+               apr_pool_destroy(temp_pool);
+               PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties");
+               return NULL;
+       }
+
+       pool = Pool();
+       if (pool == NULL)
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       if (!check_error(svn_ra_get_commit_editor2(ra->ra, &editor, 
+               &edit_baton, 
+               PyString_AsString(PyDict_GetItemString(revprops, SVN_PROP_REVISION_LOG)), py_commit_callback, 
+               commit_callback, hash_lock_tokens, keep_locks, pool))) {
+               apr_pool_destroy(pool);
+               apr_pool_destroy(temp_pool);
+               ra->busy = false;
+               return NULL;
+       }
+       apr_pool_destroy(temp_pool);
+       return new_editor_object(editor, edit_baton, pool, 
+                                                                 &Editor_Type, ra_done_handler, ra);
+}
+
+static PyObject *ra_change_rev_prop(PyObject *self, PyObject *args)
+{
+       svn_revnum_t rev;
+       char *name;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       char *value;
+       int vallen;
+    apr_pool_t *temp_pool;
+       svn_string_t *val_string;
+
+       if (!PyArg_ParseTuple(args, "lss#", &rev, &name, &value, &vallen))
+               return NULL;
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       val_string = svn_string_ncreate(value, vallen, temp_pool);
+       RUN_RA_WITH_POOL(temp_pool, ra,
+                                         svn_ra_change_rev_prop(ra->ra, rev, name, val_string, 
+                                                                                        temp_pool));
+       apr_pool_destroy(temp_pool);
+       Py_RETURN_NONE;
+}
+    
+static PyObject *ra_get_dir(PyObject *self, PyObject *args)
+{
+       apr_pool_t *temp_pool;
+    apr_hash_t *dirents;
+    apr_hash_index_t *idx;
+    apr_hash_t *props;
+    svn_revnum_t fetch_rev;
+    const char *key;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+    svn_dirent_t *dirent;
+    apr_ssize_t klen;
+       char *path;
+       svn_revnum_t revision = -1;
+       int dirent_fields = 0;
+       PyObject *py_dirents, *py_props;
+
+       if (!PyArg_ParseTuple(args, "s|li", &path, &revision, &dirent_fields))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+
+       if (revision != SVN_INVALID_REVNUM)
+               fetch_rev = revision;
+
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_dir2(ra->ra, &dirents, &fetch_rev, &props,
+                     path, revision, dirent_fields, temp_pool));
+
+       if (dirents == NULL) {
+               py_dirents = Py_None;
+       } else {
+               py_dirents = PyDict_New();
+               idx = apr_hash_first(temp_pool, dirents);
+               while (idx != NULL) {
+                       PyObject *py_dirent;
+                       apr_hash_this(idx, (const void **)&key, &klen, (void **)&dirent);
+                       py_dirent = PyDict_New();
+                       if (dirent_fields & 0x1)
+               PyDict_SetItemString(py_dirent, "kind", 
+                                                                        PyInt_FromLong(dirent->kind));
+            if (dirent_fields & 0x2)
+                               PyDict_SetItemString(py_dirent, "size", 
+                                                                        PyLong_FromLong(dirent->size));
+                       if (dirent_fields & 0x4)
+                               PyDict_SetItemString(py_dirent, "has_props",
+                                                                        PyBool_FromLong(dirent->has_props));
+                       if (dirent_fields & 0x8)
+                               PyDict_SetItemString(py_dirent, "created_rev", 
+                                                                        PyLong_FromLong(dirent->created_rev));
+                       if (dirent_fields & 0x10)
+                               PyDict_SetItemString(py_dirent, "time", 
+                                                                        PyLong_FromLong(dirent->time));
+                       if (dirent_fields & 0x20)
+                               PyDict_SetItemString(py_dirent, "last_author",
+                                                                        PyString_FromString(dirent->last_author));
+                       PyDict_SetItemString(py_dirents, key, py_dirent);
+                       idx = apr_hash_next(idx);
+               }
+       }
+
+       py_props = prop_hash_to_dict(props);
+       apr_pool_destroy(temp_pool);
+       return Py_BuildValue("(OlO)", py_dirents, fetch_rev, py_props);
+}
+
+static PyObject *ra_get_lock(PyObject *self, PyObject *args)
+{
+       char *path;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       svn_lock_t *lock;
+    apr_pool_t *temp_pool;
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra,
+                                 svn_ra_get_lock(ra->ra, &lock, path, temp_pool));
+       apr_pool_destroy(temp_pool);
+       return wrap_lock(lock);
+}
+
+static PyObject *ra_check_path(PyObject *self, PyObject *args)
+{
+       char *path; 
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       svn_revnum_t revision;
+       svn_node_kind_t kind;
+    apr_pool_t *temp_pool;
+
+       if (!PyArg_ParseTuple(args, "sl", &path, &revision))
+               return NULL;
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_RA_WITH_POOL(temp_pool, ra,
+                                         svn_ra_check_path(ra->ra, path, revision, &kind, 
+                     temp_pool));
+       apr_pool_destroy(temp_pool);
+       return PyInt_FromLong(kind);
+}
+
+static PyObject *ra_has_capability(PyObject *self, PyObject *args)
+{
+#if SVN_VER_MAJOR >= 1 && SVN_VER_MINOR >= 5
+       char *capability;
+       apr_pool_t *temp_pool;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       int has = 0;
+
+       if (!PyArg_ParseTuple(args, "s", &capability))
+               return NULL;
+       
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra,
+                                         svn_ra_has_capability(ra->ra, &has, capability, temp_pool));
+       apr_pool_destroy(temp_pool);
+       return PyBool_FromLong(has);
+#else
+       PyErr_SetString(PyExc_NotImplementedError, "has_capability is only supported in Subversion >= 1.5");
+       return NULL;
+#endif
+}
+
+static PyObject *ra_unlock(PyObject *self, PyObject *args)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       PyObject *path_tokens, *lock_func, *k, *v;
+       bool break_lock;
+       apr_ssize_t idx;
+       apr_pool_t *temp_pool;
+    apr_hash_t *hash_path_tokens;
+
+       if (!PyArg_ParseTuple(args, "ObO", &path_tokens, &break_lock, &lock_func))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       hash_path_tokens = apr_hash_make(temp_pool);
+       while (PyDict_Next(path_tokens, &idx, &k, &v)) {
+               apr_hash_set(hash_path_tokens, PyString_AsString(k), PyString_Size(k), (char *)PyString_AsString(v));
+       }
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_unlock(ra->ra, hash_path_tokens, break_lock,
+                     py_lock_func, lock_func, temp_pool));
+
+    apr_pool_destroy(temp_pool);
+       Py_RETURN_NONE;
+}
+
+static PyObject *ra_lock(PyObject *self, PyObject *args)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       PyObject *path_revs;
+       char *comment;
+       int steal_lock;
+       PyObject *lock_func, *k, *v;
+       apr_pool_t *temp_pool;
+       apr_hash_t *hash_path_revs;
+       svn_revnum_t *rev;
+       Py_ssize_t idx = 0;
+
+       if (!PyArg_ParseTuple(args, "OsbO", &path_revs, &comment, &steal_lock, 
+                                                 &lock_func))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       if (path_revs == Py_None) {
+               hash_path_revs = NULL;
+       } else {
+               hash_path_revs = apr_hash_make(temp_pool);
+       }
+
+       while (PyDict_Next(path_revs, &idx, &k, &v)) {
+               rev = (svn_revnum_t *)apr_palloc(temp_pool, sizeof(svn_revnum_t));
+               *rev = PyLong_AsLong(v);
+               apr_hash_set(hash_path_revs, PyString_AsString(k), PyString_Size(k), 
+                                        PyString_AsString(v));
+       }
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_lock(ra->ra, hash_path_revs, comment, steal_lock,
+                     py_lock_func, lock_func, temp_pool));
+       apr_pool_destroy(temp_pool);
+       return NULL;
+}
+
+static PyObject *ra_get_locks(PyObject *self, PyObject *args)
+{
+       char *path;
+       apr_pool_t *temp_pool;
+    apr_hash_t *hash_locks;
+    apr_hash_index_t *idx;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+    char *key;
+    apr_ssize_t klen;
+    svn_lock_t *lock;
+       PyObject *ret;
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_locks(ra->ra, &hash_locks, path, temp_pool));
+
+    ret = PyDict_New();
+       for (idx = apr_hash_first(temp_pool, hash_locks); idx != NULL;
+         idx = apr_hash_next(idx)) {
+               apr_hash_this(idx, (const void **)&key, &klen, (void **)&lock);
+               PyDict_SetItemString(ret, key, pyify_lock(lock));
+       }
+
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+
+static PyObject *ra_get_locations(PyObject *self, PyObject *args)
+{
+       char *path;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       svn_revnum_t peg_revision;
+       PyObject *location_revisions;
+    apr_pool_t *temp_pool;
+    apr_hash_t *hash_locations;
+    apr_hash_index_t *idx;
+    svn_revnum_t *key;
+       PyObject *ret;
+    apr_ssize_t klen;
+    char *val;
+
+       if (!PyArg_ParseTuple(args, "slO", &path, &peg_revision, &location_revisions))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_locations(ra->ra, &hash_locations,
+                    path, peg_revision, 
+                    revnum_list_to_apr_array(temp_pool, location_revisions),
+                    temp_pool));
+       ret = PyDict_New();
+
+    for (idx = apr_hash_first(temp_pool, hash_locations); idx != NULL; 
+               idx = apr_hash_next(idx)) {
+               apr_hash_this(idx, (const void **)&key, &klen, (void **)&val);
+               PyDict_SetItem(ret, PyInt_FromLong(*key), PyString_FromString(val));
+       }
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+    
+static PyObject *ra_get_file_revs(PyObject *self, PyObject *args)
+{
+       char *path;
+       svn_revnum_t start, end;
+       PyObject *file_rev_handler;
+       apr_pool_t *temp_pool;
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+
+       if (!PyArg_ParseTuple(args, "sllO", &path, &start, &end, &file_rev_handler))
+               return NULL;
+
+       if (ra_check_busy(ra))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+
+       RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_file_revs(ra->ra, path, start, end, 
+                               py_file_rev_handler, (void *)file_rev_handler, 
+                    temp_pool));
+
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static void ra_dealloc(PyObject *self)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       apr_pool_destroy(ra->pool);
+       Py_XDECREF(ra->auth);
+       PyObject_Del(self);
+}
+
+static PyObject *ra_repr(PyObject *self)
+{
+       RemoteAccessObject *ra = (RemoteAccessObject *)self;
+       return PyString_FromFormat("RemoteAccess(%s)", ra->url);
+}
+
+static PyMethodDef ra_methods[] = {
+       { "get_file_revs", ra_get_file_revs, METH_VARARGS, NULL },
+       { "get_locations", ra_get_locations, METH_VARARGS, NULL },
+       { "get_locks", ra_get_locks, METH_VARARGS, NULL },
+       { "lock", ra_lock, METH_VARARGS, NULL },
+       { "unlock", ra_unlock, METH_VARARGS, NULL },
+       { "has_capability", ra_has_capability, METH_VARARGS, NULL },
+       { "check_path", ra_check_path, METH_VARARGS, NULL },
+       { "get_lock", ra_get_lock, METH_VARARGS, NULL },
+       { "get_dir", ra_get_dir, METH_VARARGS, NULL },
+       { "change_rev_prop", ra_change_rev_prop, METH_VARARGS, NULL },
+       { "get_commit_editor", (PyCFunction)get_commit_editor, METH_VARARGS|METH_KEYWORDS, NULL },
+       { "rev_proplist", ra_rev_proplist, METH_VARARGS, NULL },
+       { "replay", ra_replay, METH_VARARGS, NULL },
+       { "do_switch", ra_do_switch, METH_VARARGS, NULL },
+       { "do_update", ra_do_update, METH_VARARGS, NULL },
+       { "get_repos_root", (PyCFunction)ra_get_repos_root, METH_VARARGS|METH_NOARGS, NULL },
+       { "get_log", (PyCFunction)ra_get_log, METH_VARARGS|METH_KEYWORDS, NULL },
+       { "get_latest_revnum", (PyCFunction)ra_get_latest_revnum, METH_NOARGS, NULL },
+       { "reparent", ra_reparent, METH_VARARGS, NULL },
+       { "get_uuid", (PyCFunction)ra_get_uuid, METH_NOARGS, NULL },
+       { NULL, }
+};
+
+static PyMemberDef ra_members[] = {
+       { "busy", T_BYTE, offsetof(RemoteAccessObject, busy), READONLY, NULL },
+       { "url", T_STRING, offsetof(RemoteAccessObject, url), READONLY, NULL },
+       { NULL, }
+};
+
+PyTypeObject RemoteAccess_Type = {
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "ra.RemoteAccess",
+       .tp_basicsize = sizeof(RemoteAccessObject),
+       .tp_new = ra_new,
+       .tp_dealloc = ra_dealloc,
+       .tp_repr = ra_repr,
+       .tp_methods = ra_methods,
+       .tp_members = ra_members,
+};
+
+typedef struct { 
+       PyObject_HEAD
+    apr_pool_t *pool;
+    svn_auth_provider_object_t *provider;
+} AuthProviderObject;
+
+static void auth_provider_dealloc(PyObject *self)
+{
+       AuthProviderObject *auth_provider = (AuthProviderObject *)self;
+       apr_pool_destroy(auth_provider->pool);
+       PyObject_Del(self);
+}
+
+PyTypeObject AuthProvider_Type = { 
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "ra.AuthProvider",
+       .tp_basicsize = sizeof(AuthProviderObject),
+       .tp_dealloc = auth_provider_dealloc,
+};
+
+static PyObject *auth_init(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+       char *kwnames[] = { "providers", NULL };
+       apr_array_header_t *c_providers;
+       svn_auth_provider_object_t **el;
+       PyObject *providers = Py_None;
+       AuthObject *ret;
+       int i;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwnames, &providers))
+               return NULL;
+
+       ret = PyObject_New(AuthObject, &Auth_Type);
+       if (ret == NULL)
+               return NULL;
+
+       ret->pool = Pool();
+       if (ret->pool == NULL)
+               return NULL;
+       ret->providers = providers;
+       Py_INCREF(providers);
+
+    c_providers = apr_array_make(ret->pool, PyList_Size(providers), 4);
+       for (i = 0; i < PyList_Size(providers); i++) {
+               AuthProviderObject *provider;
+       el = (svn_auth_provider_object_t **)apr_array_push(c_providers);
+               /* FIXME: Check that provider is indeed a AuthProviderObject object */
+        provider = (AuthProviderObject *)PyList_GetItem(providers, i);
+        *el = provider->provider;
+       }
+       svn_auth_open(&ret->auth_baton, c_providers, ret->pool);
+       return (PyObject *)ret;
+}
+
+static PyObject *auth_set_parameter(PyObject *self, PyObject *args)
+{
+       AuthObject *auth = (AuthObject *)self;
+       char *name, *value;
+       if (!PyArg_ParseTuple(args, "ss", &name, &value)) {
+        svn_auth_set_parameter(auth->auth_baton, name, (char *)value);
+       }
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *auth_get_parameter(PyObject *self, PyObject *args)
+{
+       char *name;
+       AuthObject *auth = (AuthObject *)self;
+
+       if (!PyArg_ParseTuple(args, "s", &name))
+               return NULL;
+
+       return PyString_FromString(svn_auth_get_parameter(auth->auth_baton, name));
+}
+
+static PyMethodDef auth_methods[] = {
+       { "set_parameter", auth_set_parameter, METH_VARARGS, NULL },
+       { "get_parameter", auth_get_parameter, METH_VARARGS, NULL },
+       { NULL, }
+};
+
+static void auth_dealloc(PyObject *self)
+{
+       AuthObject *auth = (AuthObject *)self;
+       apr_pool_destroy(auth->pool);
+       Py_DECREF(auth->providers);     
+}
+
+PyTypeObject Auth_Type = {
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_new = auth_init,
+       .tp_basicsize = sizeof(AuthObject),
+       .tp_dealloc = auth_dealloc,
+       .tp_name = "ra.Auth",
+       .tp_methods = auth_methods,
+};
+
+static svn_error_t *py_username_prompt(svn_auth_cred_username_t **cred, void *baton, const char *realm, int may_save, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)baton, *ret;
+       ret = PyObject_CallFunction(fn, "sb", realm, may_save);
+       if (ret == NULL)
+               return py_svn_error();
+       (*cred)->username = apr_pstrdup(pool, PyString_AsString(PyTuple_GetItem(ret, 0)));
+       (*cred)->may_save = (PyTuple_GetItem(ret, 1) == Py_True);
+    return NULL;
+}
+
+static PyObject *get_username_prompt_provider(PyObject *self, PyObject *args)
+{
+    AuthProviderObject *auth;
+       PyObject *prompt_func;
+       int retry_limit;
+       if (!PyArg_ParseTuple(args, "Oi", &prompt_func, &retry_limit))
+               return NULL;
+    auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_username_prompt_provider(&auth->provider, py_username_prompt, (void *)prompt_func, retry_limit, auth->pool);
+    return (PyObject *)auth;
+}
+
+static svn_error_t *py_simple_prompt(svn_auth_cred_simple_t **cred, void *baton, const char *realm, const char *username, int may_save, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)baton, *ret;
+       ret = PyObject_CallFunction(fn, "ssb", realm, username, may_save);
+       if (ret == NULL)
+               return py_svn_error();
+       /* FIXME: Check type of ret */
+    (*cred)->username = apr_pstrdup(pool, PyString_AsString(PyTuple_GetItem(ret, 0)));
+    (*cred)->password = apr_pstrdup(pool, PyString_AsString(PyTuple_GetItem(ret, 1)));
+       (*cred)->may_save = (PyTuple_GetItem(ret, 2) == Py_True);
+    return NULL;
+}
+
+static PyObject *get_simple_prompt_provider(PyObject *self, PyObject *args)
+{
+       PyObject *prompt_func;
+       int retry_limit;
+    AuthProviderObject *auth;
+
+       if (!PyArg_ParseTuple(args, "Oi", &prompt_func, &retry_limit))
+               return NULL;
+
+    auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_simple_prompt_provider (&auth->provider, py_simple_prompt, (void *)prompt_func, retry_limit, auth->pool);
+    return (PyObject *)auth;
+}
+
+static svn_error_t *py_ssl_server_trust_prompt(svn_auth_cred_ssl_server_trust_t **cred, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *cert_info, svn_boolean_t may_save, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)baton;
+       PyObject *ret;
+
+       ret = PyObject_CallFunction(fn, "sl(ssssss)b", realm, failures, 
+                                                 cert_info->hostname, cert_info->fingerprint, 
+                                                 cert_info->valid_from, cert_info->valid_until, 
+                                                 cert_info->issuer_dname, cert_info->ascii_cert, 
+                                                 may_save);
+       if (ret == NULL)
+               return py_svn_error();
+
+       /* FIXME: Check that ret is a tuple of size 2 */
+
+       (*cred)->may_save = (PyTuple_GetItem(ret, 0) == Py_True);
+       (*cred)->accepted_failures = PyLong_AsLong(PyTuple_GetItem(ret, 1));
+
+    return NULL;
+}
+
+static PyObject *get_ssl_server_trust_prompt_provider(PyObject *self, PyObject *args)
+{
+    AuthProviderObject *auth;
+       PyObject *prompt_func;
+
+       if (!PyArg_ParseTuple(args, "O", &prompt_func))
+               return NULL;
+
+    auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+       if (auth == NULL)
+               return NULL;
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_ssl_server_trust_prompt_provider (&auth->provider, py_ssl_server_trust_prompt, (void *)prompt_func, auth->pool);
+    return (PyObject *)auth;
+}
+
+static svn_error_t *py_ssl_client_cert_pw_prompt(svn_auth_cred_ssl_client_cert_pw_t **cred, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)baton, *ret;
+       ret = PyObject_CallFunction(fn, "sb", realm, may_save);
+       if (ret == NULL) 
+               return py_svn_error();
+       /* FIXME: Check ret is a tuple of size 2 */
+       (*cred)->password = apr_pstrdup(pool, PyString_AsString(PyTuple_GetItem(ret, 0)));
+       (*cred)->may_save = (PyTuple_GetItem(ret, 1) == Py_True);
+    return NULL;
+}
+
+static PyObject *get_ssl_client_cert_pw_prompt_provider(PyObject *self, PyObject *args)
+{
+       PyObject *prompt_func;
+       int retry_limit;
+    AuthProviderObject *auth;
+
+       if (!PyArg_ParseTuple(args, "Oi", &prompt_func, &retry_limit))
+               return NULL;
+
+    auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+       if (auth == NULL)
+               return NULL;
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_ssl_client_cert_pw_prompt_provider (&auth->provider, py_ssl_client_cert_pw_prompt, (void *)prompt_func, retry_limit, auth->pool);
+    return (PyObject *)auth;
+}
+
+static PyObject *get_username_provider(PyObject *self)
+{
+    AuthProviderObject *auth;
+    auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+       if (auth == NULL)
+               return NULL;
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_username_provider(&auth->provider, auth->pool);
+    return (PyObject *)auth;
+}
+
+static PyObject *get_simple_provider(PyObject *self)
+{
+    AuthProviderObject *auth = PyObject_New(AuthProviderObject, 
+                                                                                       &AuthProvider_Type);
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_simple_provider(&auth->provider, auth->pool);
+    return (PyObject *)auth;
+}
+
+static PyObject *get_ssl_server_trust_file_provider(PyObject *self)
+{
+    AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_ssl_server_trust_file_provider(&auth->provider, auth->pool);
+    return (PyObject *)auth;
+}
+
+static PyObject *get_ssl_client_cert_file_provider(PyObject *self)
+{
+    AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_ssl_client_cert_file_provider(&auth->provider, auth->pool);
+    return (PyObject *)auth;
+}
+
+static PyObject *get_ssl_client_cert_pw_file_provider(PyObject *self)
+{
+    AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type);
+    auth->pool = Pool();
+       if (auth->pool == NULL)
+               return NULL;
+    svn_auth_get_ssl_client_cert_pw_file_provider(&auth->provider, auth->pool);
+    return (PyObject *)auth;
+}
+
+static PyObject *txdelta_send_stream(PyObject *self, PyObject *args)
+{
+    unsigned char digest[16];
+    apr_pool_t *pool;
+       PyObject *stream;
+       TxDeltaWindowHandlerObject *py_txdelta;
+
+       if (!PyArg_ParseTuple(args, "OO", &stream, &py_txdelta))
+               return NULL;
+
+       pool = Pool();
+
+       if (pool == NULL)
+               return NULL;
+
+    if (!check_error(svn_txdelta_send_stream(new_py_stream(pool, stream), py_txdelta->txdelta_handler, py_txdelta->txdelta_baton, (unsigned char *)digest, pool))) {
+               apr_pool_destroy(pool);
+               return NULL;
+       }
+    apr_pool_destroy(pool);
+    return PyString_FromStringAndSize((char *)digest, 16);
+}
+
+static PyMethodDef ra_module_methods[] = {
+       { "version", (PyCFunction)version, METH_NOARGS, NULL },
+       { "txdelta_send_stream", (PyCFunction)txdelta_send_stream, METH_VARARGS, NULL },
+       { "get_ssl_client_cert_pw_file_provider", (PyCFunction)get_ssl_client_cert_pw_file_provider, METH_NOARGS, NULL },
+       { "get_ssl_client_cert_file_provider", (PyCFunction)get_ssl_client_cert_file_provider, METH_NOARGS, NULL },
+       { "get_ssl_server_trust_file_provider", (PyCFunction)get_ssl_server_trust_file_provider, METH_NOARGS, NULL },
+       { "get_simple_provider", (PyCFunction)get_simple_provider, METH_NOARGS, NULL },
+       { "get_username_prompt_provider", (PyCFunction)get_username_prompt_provider, METH_VARARGS, NULL },
+       { "get_simple_prompt_provider", (PyCFunction)get_simple_prompt_provider, METH_VARARGS, NULL },
+       { "get_ssl_server_trust_prompt_provider", (PyCFunction)get_ssl_server_trust_prompt_provider, METH_VARARGS, NULL },
+       { "get_ssl_client_cert_pw_prompt_provider", (PyCFunction)get_ssl_client_cert_pw_prompt_provider, METH_VARARGS, NULL },
+       { "get_username_provider", (PyCFunction)get_username_provider, METH_NOARGS, NULL },
+       { NULL, }
+};
+
+void initra(void)
+{
+       static apr_pool_t *pool;
+       PyObject *mod;
+
+       if (PyType_Ready(&RemoteAccess_Type) < 0)
+               return;
+
+       if (PyType_Ready(&Editor_Type) < 0)
+               return;
+
+       if (PyType_Ready(&FileEditor_Type) < 0)
+               return;
+
+       if (PyType_Ready(&DirectoryEditor_Type) < 0)
+               return;
+
+       if (PyType_Ready(&Reporter_Type) < 0)
+               return;
+
+       if (PyType_Ready(&TxDeltaWindowHandler_Type) < 0)
+               return;
+
+       if (PyType_Ready(&Auth_Type) < 0)
+               return;
+
+       if (PyType_Ready(&AuthProvider_Type) < 0)
+               return;
+
+       apr_initialize();
+       pool = Pool();
+       if (pool == NULL)
+               return;
+       svn_ra_initialize(pool);
+
+       mod = Py_InitModule3("ra", ra_module_methods, "Remote Access");
+       if (mod == NULL)
+               return;
+
+       PyModule_AddObject(mod, "RemoteAccess", (PyObject *)&RemoteAccess_Type);
+       Py_INCREF(&RemoteAccess_Type);
+
+       PyModule_AddObject(mod, "Auth", (PyObject *)&Auth_Type);
+       Py_INCREF(&Auth_Type);
+
+       busy_exc = PyErr_NewException("ra.BusyException", NULL, NULL);
+       PyModule_AddObject(mod, "BusyException", busy_exc);
+
+    PyModule_AddIntConstant(mod, "DIRENT_KIND", SVN_DIRENT_KIND);
+    PyModule_AddIntConstant(mod, "DIRENT_SIZE", SVN_DIRENT_SIZE);
+    PyModule_AddIntConstant(mod, "DIRENT_HAS_PROPS", SVN_DIRENT_HAS_PROPS);
+    PyModule_AddIntConstant(mod, "DIRENT_CREATED_REV", SVN_DIRENT_CREATED_REV);
+    PyModule_AddIntConstant(mod, "DIRENT_TIME", SVN_DIRENT_TIME);
+    PyModule_AddIntConstant(mod, "DIRENT_LAST_AUTHOR", SVN_DIRENT_LAST_AUTHOR);
+    PyModule_AddIntConstant(mod, "DIRENT_ALL", SVN_DIRENT_ALL);
+}
index 9c5c5de7f0f83e4fe612a0ccc8678228adc367eb..85815d21b5d04a9d09ef6fa628cfd6c57c5d6f9d 100644 (file)
--- a/remote.py
+++ b/remote.py
@@ -22,8 +22,8 @@ from bzrlib.errors import (NotBranchError, NotLocalUrl, NoRepositoryPresent,
                            NoWorkingTree, AlreadyBranchError)
 from bzrlib.transport.local import LocalTransport
 
-from svn.core import SubversionException
-import svn.core, svn.repos
+from core import SubversionException
+import core 
 
 from bzrlib.plugins.svn.errors import NoSvnRepositoryPresent
 from bzrlib.plugins.svn.format import get_rich_root_format, SvnRemoteFormat
@@ -127,7 +127,7 @@ class SvnRemoteAccess(BzrDir):
             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:
diff --git a/repos.c b/repos.c
new file mode 100644 (file)
index 0000000..d4ce63b
--- /dev/null
+++ b/repos.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <stdbool.h>
+#include <Python.h>
+#include <apr_general.h>
+#include <svn_fs.h>
+#include <svn_repos.h>
+
+#include "util.h"
+
+PyAPI_DATA(PyTypeObject) Repository_Type;
+PyAPI_DATA(PyTypeObject) FileSystem_Type;
+
+typedef struct { 
+       PyObject_HEAD
+    apr_pool_t *pool;
+    svn_repos_t *repos;
+} RepositoryObject;
+
+static PyObject *repos_create(PyObject *self, PyObject *args)
+{
+       char *path;
+       PyObject *config=Py_None, *fs_config=Py_None;
+    svn_repos_t *repos;
+    apr_pool_t *pool;
+    apr_hash_t *hash_config, *hash_fs_config;
+       RepositoryObject *ret;
+
+       if (!PyArg_ParseTuple(args, "s|OO", &path, &config, &fs_config))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    hash_config = NULL; /* FIXME */
+    hash_fs_config = NULL; /* FIXME */
+    RUN_SVN_WITH_POOL(pool, svn_repos_create(&repos, path, "", "", 
+                hash_config, hash_fs_config, pool));
+
+       ret = PyObject_New(RepositoryObject, &Repository_Type);
+       if (ret == NULL)
+               return NULL;
+
+       ret->pool = pool;
+       ret->repos = repos;
+
+    return (PyObject *)ret;
+}
+
+static void repos_dealloc(PyObject *self)
+{
+       RepositoryObject *repos = (RepositoryObject *)self;
+
+       apr_pool_destroy(repos->pool);
+}
+
+static PyObject *repos_init(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+       char *path;
+       char *kwnames[] = { "path", NULL };
+       RepositoryObject *ret;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwnames, &path))
+               return NULL;
+
+       ret = PyObject_New(RepositoryObject, &Repository_Type);
+       if (ret == NULL)
+               return NULL;
+
+       ret->pool = Pool();
+       if (ret->pool == NULL)
+               return NULL;
+    if (!check_error(svn_repos_open(&ret->repos, path, ret->pool))) {
+               apr_pool_destroy(ret->pool);
+               PyObject_Del(ret);
+               return NULL;
+       }
+
+       return (PyObject *)ret;
+}
+
+typedef struct {
+       PyObject_HEAD
+       RepositoryObject *repos;
+       apr_pool_t *pool;
+       svn_fs_t *fs;
+} FileSystemObject;
+
+static PyObject *repos_fs(PyObject *self)
+{
+       RepositoryObject *reposobj = (RepositoryObject *)self;
+       FileSystemObject *ret;
+       svn_fs_t *fs;
+
+       fs = svn_repos_fs(reposobj->repos);
+
+       ret = PyObject_New(FileSystemObject, &FileSystem_Type);
+       if (ret == NULL)
+               return NULL;
+
+       ret->fs = fs;
+       ret->repos = reposobj;
+       ret->pool = reposobj->pool;
+       Py_INCREF(reposobj);
+
+       return (PyObject *)ret;
+}
+
+static PyObject *fs_get_uuid(PyObject *self)
+{
+       FileSystemObject *fsobj = (FileSystemObject *)self;
+       const char *uuid;
+       PyObject *ret;
+       apr_pool_t *temp_pool;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_fs_get_uuid(fsobj->fs, &uuid, temp_pool));
+       ret = PyString_FromString(uuid);
+       apr_pool_destroy(temp_pool);
+
+       return ret;
+}
+
+static PyMethodDef fs_methods[] = {
+       { "get_uuid", (PyCFunction)fs_get_uuid, METH_NOARGS, NULL },
+       { NULL, }
+};
+
+static void fs_dealloc(PyObject *self)
+{
+       FileSystemObject *fsobj = (FileSystemObject *)self;
+
+       Py_DECREF(fsobj->repos);
+       apr_pool_destroy(fsobj->pool);
+}
+
+PyTypeObject FileSystem_Type = {
+       PyObject_HEAD_INIT(NULL) 0,
+       .tp_name = "repos.FileSystem",
+       .tp_basicsize = sizeof(FileSystemObject),
+       .tp_dealloc = fs_dealloc,
+       .tp_methods = fs_methods,
+};
+
+static PyObject *repos_load_fs(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+       const char *parent_dir = "";
+       PyObject *dumpstream, *feedback_stream, *cancel_func = Py_None;
+       bool use_pre_commit_hook = false, use_post_commit_hook = false;
+       char *kwnames[] = { "dumpstream", "feedback_stream", "uuid_action",
+                               "parent_dir", "use_pre_commit_hook", 
+                                               "use_post_commit_hook", "cancel_func", NULL };
+       int uuid_action;
+       apr_pool_t *temp_pool;
+       RepositoryObject *reposobj = (RepositoryObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOi|sbbO", kwnames,
+                                                               &dumpstream, &feedback_stream, &uuid_action,
+                                                               &parent_dir, &use_pre_commit_hook, 
+                                                               &use_post_commit_hook,
+                                                               &cancel_func))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_repos_load_fs2(reposobj->repos, 
+                               new_py_stream(temp_pool, dumpstream), 
+                               new_py_stream(temp_pool, feedback_stream),
+                               uuid_action, parent_dir, use_pre_commit_hook, 
+                               use_post_commit_hook, py_cancel_func, (void *)cancel_func,
+                               reposobj->pool));
+       apr_pool_destroy(temp_pool);
+       Py_RETURN_NONE;
+}
+
+static PyMethodDef repos_module_methods[] = {
+       { "create", repos_create, METH_VARARGS, NULL },
+       { NULL, }
+};
+
+static PyMethodDef repos_methods[] = {
+       { "load_fs", (PyCFunction)repos_load_fs, METH_VARARGS|METH_KEYWORDS, NULL },
+       { "fs", (PyCFunction)repos_fs, METH_NOARGS, NULL },
+       { NULL, }
+};
+
+PyTypeObject Repository_Type = {
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "repos.Repository",
+       .tp_basicsize = sizeof(RepositoryObject),
+       .tp_dealloc = repos_dealloc,
+       .tp_methods = repos_methods,
+       .tp_new = repos_init,
+};
+
+void initrepos(void)
+{
+       static apr_pool_t *pool;
+       PyObject *mod;
+
+       if (PyType_Ready(&Repository_Type) < 0)
+               return;
+
+       if (PyType_Ready(&FileSystem_Type) < 0)
+               return;
+
+       apr_initialize();
+       pool = Pool();
+       if (pool == NULL)
+               return;
+       svn_fs_initialize(pool);
+
+       mod = Py_InitModule3("repos", repos_module_methods, "Local repository management");
+       if (mod == NULL)
+               return;
+
+       PyModule_AddObject(mod, "LOAD_UUID_DEFAULT", PyLong_FromLong(0));
+       PyModule_AddObject(mod, "LOAD_UUID_IGNORE", PyLong_FromLong(1));
+       PyModule_AddObject(mod, "LOAD_UUID_FORCE", PyLong_FromLong(2));
+
+       PyModule_AddObject(mod, "Repository", (PyObject *)&Repository_Type);
+       Py_INCREF(&Repository_Type);
+}
index 79dcb80f5324f6f6034090158c11f38d7d5f08b4..402b13ade90f57de8989b602e3d4e1c0950d0eb1 100644 (file)
@@ -19,7 +19,8 @@ import bzrlib
 from bzrlib import osutils, ui, urlutils, xml5
 from bzrlib.branch import Branch, BranchCheckResult
 from bzrlib.errors import (InvalidRevisionId, NoSuchRevision, NotBranchError, 
-                           UninitializableFormat, UnrelatedBranches)
+                           UninitializableFormat, UnrelatedBranches, 
+                           NotWriteLocked)
 from bzrlib.graph import CachingParentsProvider
 from bzrlib.inventory import Inventory
 from bzrlib.lockable_files import LockableFiles, TransportLock
@@ -29,19 +30,16 @@ from bzrlib.revision import Revision, NULL_REVISION, ensure_null
 from bzrlib.transport import Transport, get_transport
 from bzrlib.trace import info, mutter
 
-from svn.core import SubversionException
-import svn.core
 
 import os
 
+from bzrlib.plugins.svn import changes, core, errors, logwalker
 from bzrlib.plugins.svn.branchprops import PathPropertyProvider
 from bzrlib.plugins.svn.cache import create_cache_dir, sqlite3
-from bzrlib.plugins.svn import changes
+from bzrlib.plugins.svn.core import SubversionException
 from bzrlib.plugins.svn.changes import changes_path, find_prev_location
 from bzrlib.plugins.svn.config import SvnRepositoryConfig
 from bzrlib.plugins.svn.parents import SqliteCachingParentsProvider
-from bzrlib.plugins.svn import errors
-from bzrlib.plugins.svn import logwalker
 from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_REVISION_ID, SVN_REVPROP_BZR_SIGNATURE,
                      parse_revision_metadata, parse_revid_property, 
                      parse_merge_property, BzrSvnMapping,
@@ -378,7 +376,7 @@ class SvnRepository(Repository):
             return False
 
         try:
-            return (svn.core.svn_node_dir == self.transport.check_path(path, revnum))
+            return (core.NODE_DIR == self.transport.check_path(path, revnum))
         except SubversionException, (_, num):
             if num == errors.ERR_FS_NO_SUCH_REVISION:
                 return False
@@ -456,7 +454,6 @@ class SvnRepository(Repository):
             parent_ids = (mainline_parent,)
             
             if mainline_parent != NULL_REVISION:
-
                 svn_fileprops = logwalker.lazy_dict({}, self.branchprop_list.get_changed_properties, branch, revnum)
                 svn_revprops = logwalker.lazy_dict({}, self.transport.revprop_list, revnum)
                 revmeta = RevisionMetadata(self, branch, None, revnum, svn_revprops, svn_fileprops)
@@ -781,3 +778,13 @@ class SvnRepository(Repository):
         else:
             assert False
 
+    def abort_write_group(self):
+        self._write_group = None
+
+    def commit_write_group(self):
+        self._write_group = None
+
+    def start_write_group(self):
+        if not self.is_write_locked():
+            raise NotWriteLocked(self)
+        self._write_group = None 
index 8800373508e197a5ad6e5e46d6ee08c85b511bcf..41cee7695703c1b0ac4d5a3997a8b3368b811aeb 100644 (file)
--- a/revids.py
+++ b/revids.py
@@ -20,9 +20,8 @@ from bzrlib import debug
 from bzrlib.errors import (InvalidRevisionId, NoSuchRevision)
 from bzrlib.trace import mutter
 
-import svn.core
-from svn.core import SubversionException
-
+from bzrlib.plugins.svn import core
+from bzrlib.plugins.svn.core import SubversionException
 from bzrlib.plugins.svn.cache import CacheTable
 from bzrlib.plugins.svn.errors import InvalidPropertyValue, ERR_FS_NO_SUCH_REVISION, ERR_FS_NOT_DIRECTORY
 from bzrlib.plugins.svn.mapping import (parse_revision_id, BzrSvnMapping, 
index e6a277259fbe9bc1fdcfcffd2d205f031a712c7d..0be0f92a4177046438dfadb07e91ddac5d5330de 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,26 @@
 #!/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
+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',
@@ -20,5 +40,16 @@ setup(name='bzr-svn',
                    'bzrlib.plugins.svn.tests':'tests'},
       packages=['bzrlib.plugins.svn', 
                 'bzrlib.plugins.svn.mapping3', 
-                'bzrlib.plugins.svn.tests']
+                'bzrlib.plugins.svn.tests'],
+      ext_modules=[
+          Extension("core", ["core.c", "util.c"], libraries=["svn_subr-1"], 
+                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
+          Extension("client", ["client.c", "util.c"], libraries=["svn_client-1"], 
+                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
+          Extension("ra", ["ra.c", "util.c", "editor.c"], libraries=["svn_ra-1"], 
+                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
+          Extension("repos", ["repos.c", "util.c"], libraries=["svn_repos-1"], 
+                    include_dirs=[apr_include_dir(), svn_include_dir()]), 
+          Extension("wc", ["wc.c", "util.c", "editor.c"], libraries=["svn_wc-1"],
+                     include_dirs=[apr_include_dir(), svn_include_dir()])],
       )
index 7b44eb6f241175ea78843c65d9d7c256d57cdf98..d4eb639c1534957684a8d7a33b797166e5192a5d 100644 (file)
@@ -24,13 +24,12 @@ from cStringIO import StringIO
 
 from bzrlib import osutils, urlutils
 from bzrlib.bzrdir import BzrDir
+from bzrlib.plugins.svn.ra import RemoteAccess, txdelta_send_stream
 from bzrlib.tests import TestCaseInTempDir, TestSkipped
 from bzrlib.trace import mutter
-from bzrlib.urlutils import local_path_to_url
 from bzrlib.workingtree import WorkingTree
 
-import svn.repos, svn.wc
-from bzrlib.plugins.svn.errors import NoCheckoutSupport
+from bzrlib.plugins.svn import repos, wc, client, ra, properties
 
 class TestCaseWithSubversionRepository(TestCaseInTempDir):
     """A test case that provides the ability to build Subversion 
@@ -38,12 +37,11 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
 
     def setUp(self):
         super(TestCaseWithSubversionRepository, self).setUp()
-        self.client_ctx = svn.client.create_context()
-        self.client_ctx.log_msg_func2 = svn.client.svn_swig_py_get_commit_log_func
-        self.client_ctx.log_msg_baton2 = self.log_message_func
+        self.client_ctx = client.Client()
+        self.client_ctx.log_msg_func = self.log_message_func
 
-    def log_message_func(self, items, pool):
-        return self.next_message
+    def log_message_func(self, items):
+        return (self.next_message, None)
 
     def make_repository(self, relpath, allow_revprop_changes=True):
         """Create a repository.
@@ -52,7 +50,7 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
         """
         abspath = os.path.join(self.test_dir, relpath)
 
-        svn.repos.create(abspath, '', '', None, None)
+        repos.create(abspath)
 
         if allow_revprop_changes:
             if sys.platform == 'win32':
@@ -63,7 +61,7 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
                 open(revprop_hook, 'w').write("#!/bin/sh\n")
                 os.chmod(revprop_hook, os.stat(revprop_hook).st_mode | 0111)
 
-        return local_path_to_url(abspath)
+        return urlutils.local_path_to_url(abspath)
 
     def make_remote_bzrdir(self, relpath):
         """Create a repository."""
@@ -84,79 +82,49 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
 
         repos_url = self.make_repository(repos_path)
 
-        try:
-            return self.open_local_bzrdir(repos_url, relpath)
-        except NoCheckoutSupport:
-            raise TestSkipped('No Checkout Support')
+        return self.open_local_bzrdir(repos_url, relpath)
 
 
     def make_checkout(self, repos_url, relpath):
-        rev = svn.core.svn_opt_revision_t()
-        rev.kind = svn.core.svn_opt_revision_head
-
-        svn.client.checkout2(repos_url, relpath, 
-                rev, rev, True, False, self.client_ctx)
+        self.client_ctx.checkout(repos_url, relpath, "HEAD", "HEAD", 
+                                 True, False)
 
     @staticmethod
     def create_checkout(branch, path, revision_id=None, lightweight=False):
-        try:
-            return branch.create_checkout(path, revision_id=revision_id,
+        return branch.create_checkout(path, revision_id=revision_id,
                                           lightweight=lightweight)
-        except NoCheckoutSupport:
-            raise TestSkipped('No Checkout Support')
 
     @staticmethod
     def open_checkout(url):
-        try:
-            return WorkingTree.open(url)
-        except NoCheckoutSupport:
-           raise TestSkipped('No Checkout Support')
+        return WorkingTree.open(url)
 
     @staticmethod
     def open_checkout_bzrdir(url):
-        try:
-            return BzrDir.open(url)
-        except NoCheckoutSupport:
-           raise TestSkipped('No Checkout Support')
+        return BzrDir.open(url)
 
     @staticmethod
     def create_branch_convenience(url):
-        try:
-            return BzrDir.create_branch_convenience(url)
-        except NoCheckoutSupport:
-           raise TestSkipped('No Checkout Support')
+        return BzrDir.create_branch_convenience(url)
 
     def client_set_prop(self, path, name, value):
         if value is None:
             value = ""
-        svn.client.propset2(name, value, path, False, True, self.client_ctx)
+        self.client_ctx.propset(name, value, path, False, True)
 
     def client_get_prop(self, path, name, revnum=None, recursive=False):
-        rev = svn.core.svn_opt_revision_t()
-
         if revnum is None:
-            rev.kind = svn.core.svn_opt_revision_working
-        else:
-            rev.kind = svn.core.svn_opt_revision_number
-            rev.value.number = revnum
-        ret = svn.client.propget2(name, path, rev, rev, recursive, 
-                                  self.client_ctx)
+            revnum = "WORKING"
+        ret = self.client_ctx.propget(name, path, revnum, revnum, recursive)
         if recursive:
             return ret
         else:
             return ret.values()[0]
 
     def client_get_revprop(self, url, revnum, name):
-        rev = svn.core.svn_opt_revision_t()
-        rev.kind = svn.core.svn_opt_revision_number
-        rev.value.number = revnum
-        return svn.client.revprop_get(name, url, rev, self.client_ctx)[0]
+        return self.client_ctx.revprop_get(name, url, revnum)[0]
 
     def client_set_revprop(self, url, revnum, name, value):
-        rev = svn.core.svn_opt_revision_t()
-        rev.kind = svn.core.svn_opt_revision_number
-        rev.value.number = revnum
-        svn.client.revprop_set(name, value, url, rev, True, self.client_ctx)
+        return self.client_ctx.revprop_set(name, value, url, revnum)
         
     def client_commit(self, dir, message=None, recursive=True):
         """Commit current changes in specified working copy.
@@ -165,40 +133,28 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
         """
         olddir = os.path.abspath('.')
         self.next_message = message
-        os.chdir(dir)
-        info = svn.client.commit2(["."], recursive, False, self.client_ctx)
-        os.chdir(olddir)
+        info = self.client_ctx.commit([dir], recursive, False)
         assert info is not None
-        return (info.revision, info.date, info.author)
+        return info
 
     def client_add(self, relpath, recursive=True):
         """Add specified files to working copy.
         
         :param relpath: Path to the files to add.
         """
-        svn.client.add3(relpath, recursive, False, False, self.client_ctx)
+        self.client_ctx.add(relpath, recursive, False, False)
 
-    def revnum_to_opt_rev(self, revnum):
-        rev = svn.core.svn_opt_revision_t()
-        if revnum is None:
-            rev.kind = svn.core.svn_opt_revision_head
-        else:
-            assert isinstance(revnum, int)
-            rev.kind = svn.core.svn_opt_revision_number
-            rev.value.number = revnum
-        return rev
-
-    def client_log(self, path, start_revnum=None, stop_revnum=None):
-        assert isinstance(path, str)
+    def client_log(self, url, start_revnum=0, stop_revnum=-1):
+        ra = RemoteAccess(url.encode("utf-8"))
         ret = {}
-        def rcvr(orig_paths, rev, author, date, message, pool):
-            ret[rev] = (orig_paths, author, date, message)
-        svn.client.log([path], self.revnum_to_opt_rev(start_revnum),
-                       self.revnum_to_opt_rev(stop_revnum),
-                       True,
-                       True,
-                       rcvr,
-                       self.client_ctx)
+        def rcvr(orig_paths, rev, revprops):
+            ret[rev] = (orig_paths, 
+                        revprops.get(properties.PROP_REVISION_AUTHOR),
+                        revprops.get(properties.PROP_REVISION_DATE),
+                        revprops.get(properties.PROP_REVISION_LOG))
+        if stop_revnum == -1:
+            stop_revnum = ra.get_latest_revnum()
+        ra.get_log(rcvr, None, start_revnum, stop_revnum, 0, True, True)
         return ret
 
     def client_delete(self, relpath):
@@ -206,7 +162,7 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
 
         :param relpath: Path to the files to remove.
         """
-        svn.client.delete2([relpath], True, self.client_ctx)
+        self.client_ctx.delete([relpath], True)
 
     def client_copy(self, oldpath, newpath, revnum=None):
         """Copy file in working copy.
@@ -214,18 +170,10 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
         :param oldpath: Relative path to original file.
         :param newpath: Relative path to new file.
         """
-        rev = svn.core.svn_opt_revision_t()
-        if revnum is None:
-            rev.kind = svn.core.svn_opt_revision_head
-        else:
-            rev.kind = svn.core.svn_opt_revision_number
-            rev.value.number = revnum
-        svn.client.copy2(oldpath, rev, newpath, self.client_ctx)
+        self.client_ctx.copy(oldpath, newpath, revnum)
 
     def client_update(self, path):
-        rev = svn.core.svn_opt_revision_t()
-        rev.kind = svn.core.svn_opt_revision_head
-        svn.client.update(path, rev, True, self.client_ctx)
+        self.client_ctx.update([path], None, True)
 
     def build_tree(self, files):
         """Create a directory tree.
@@ -276,19 +224,15 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
 
         :return: FS.
         """
-        repos = svn.repos.open(relpath)
-
-        return svn.repos.fs(repos)
+        return repos.Repository(relpath).fs()
 
     def commit_editor(self, url, message="Test commit"):
-        ra = svn.client.open_ra_session(url.encode('utf8'), 
-                    self.client_ctx)
+        ra = RemoteAccess(url.encode('utf8'))
         class CommitEditor:
-            def __init__(self, ra, editor, edit_baton, base_revnum, base_url):
+            def __init__(self, ra, edit_baton, base_revnum, base_url):
                 self._used = False
                 self.ra = ra
                 self.base_revnum = base_revnum
-                self.editor = editor
                 self.edit_baton = edit_baton
                 self.data = {}
                 self.create = set()
@@ -350,49 +294,48 @@ class TestCaseWithSubversionRepository(TestCaseInTempDir):
                 for name, contents in dir_dict.items():
                     subpath = urlutils.join(path, name).strip("/")
                     if contents is None:
-                        svn.delta.editor_invoke_delete_entry(self.editor, subpath, -1, dir_baton)
+                        dir_baton.delete_entry(subpath, -1)
                     elif isinstance(contents, dict):
                         if subpath in self.create:
-                            child_baton = svn.delta.editor_invoke_add_directory(self.editor, subpath, dir_baton, self.copyfrom[subpath][0], self.copyfrom[subpath][1])
+                            child_baton = dir_baton.add_directory(subpath, self.copyfrom[subpath][0], self.copyfrom[subpath][1])
                         else:
-                            child_baton = svn.delta.editor_invoke_open_directory(self.editor, subpath, dir_baton, -1)
+                            child_baton = dir_baton.open_directory(subpath, -1)
                         if subpath in self.props:
                             for k, v in self.props[subpath].items():
-                                svn.delta.editor_invoke_change_dir_prop(self.editor, child_baton, k, v)
+                                child_baton.change_prop(k, v)
 
                         self._process_dir(child_baton, dir_dict[name], subpath)
 
-                        svn.delta.editor_invoke_close_directory(self.editor, child_baton)
+                        child_baton.close()
                     else:
                         if subpath in self.create:
-                            child_baton = svn.delta.editor_invoke_add_file(self.editor, subpath, dir_baton, None, -1)
+                            child_baton = dir_baton.add_file(subpath, None, -1)
                         else:
-                            child_baton = svn.delta.editor_invoke_open_file(self.editor, subpath, dir_baton, -1)
+                            child_baton = dir_baton.open_file(subpath, -1)
                         if isinstance(contents, str):
-                            (txdelta, txbaton) = svn.delta.editor_invoke_apply_textdelta(self.editor, child_baton, None)
-                            svn.delta.svn_txdelta_send_stream(StringIO(contents), txdelta, txbaton)
+                            txdelta = child_baton.apply_textdelta(None)
+                            txdelta_send_stream(StringIO(contents), txdelta)
                         if subpath in self.props:
                             for k, v in self.props[subpath].items():
-                                svn.delta.editor_invoke_change_file_prop(self.editor, child_baton, k, v)
-                        svn.delta.editor_invoke_close_file(self.editor, child_baton, None)
+                                child_baton.change_prop(k, v)
+                        child_baton.close(None)
 
             def done(self):
                 assert self._used == False
                 self._used = True
-                root_baton = svn.delta.editor_invoke_open_root(self.editor, self.edit_baton, 
-                                                               self.base_revnum)
+                root_baton = self.edit_baton.open_root(self.base_revnum)
                 self._process_dir(root_baton, self.data, "")
-                svn.delta.editor_invoke_close_directory(self.editor, root_baton)
-                svn.delta.editor_invoke_close_edit(self.editor, self.edit_baton)
+                root_baton.close()
+                self.edit_baton.close()
 
-                my_revnum = svn.ra.get_latest_revnum(ra)
+                my_revnum = self.ra.get_latest_revnum()
                 assert my_revnum > self.base_revnum
 
                 return my_revnum
 
-        base_revnum = svn.ra.get_latest_revnum(ra)
-        editor, edit_baton = svn.ra.get_commit_editor(ra, message, None, None, True)
-        return CommitEditor(ra, editor, edit_baton, base_revnum, url)
+        base_revnum = ra.get_latest_revnum()
+        edit_baton = ra.get_commit_editor({"svn:log": message})
+        return CommitEditor(ra, edit_baton, base_revnum, url)
 
 
 def test_suite():
@@ -409,6 +352,8 @@ def test_suite():
             'test_branchprops', 
             'test_changes',
             'test_checkout',
+            'test_client',
+            'test_core',
             'test_commit',
             'test_config',
             'test_convert',
@@ -418,8 +363,10 @@ def test_suite():
             'test_logwalker',
             'test_mapping',
             'test_push',
+            'test_ra',
             'test_radir',
             'test_repos', 
+            'test_repository', 
             'test_revids',
             'test_revspec',
             'test_scheme', 
@@ -427,6 +374,7 @@ def test_suite():
             'test_transport',
             'test_tree',
             'test_upgrade',
+            'test_wc',
             'test_workingtree',
             'test_blackbox']
     suite.addTest(loader.loadTestsFromModuleNames(["%s.%s" % (__name__, i) for i in testmod_names]))
index aae06427b0995396244423f55d72c949dba36dc0..0109bc77ba92aefc09ff8bac8db5a0e4a1bc80bc 100644 (file)
@@ -596,7 +596,6 @@ foohosts""")
         repos_url = self.make_repository('d')
 
         dc = self.commit_editor(repos_url)
-
         dc.add_dir("trunk")
         dc.add_file("trunk/hosts")
         dc.done()
index 3afef23aef32ca961fe2199822b18a0a23ea2433..a0cd5afc05931a44c59f6f9f93f51d727583043d 100644 (file)
@@ -29,10 +29,10 @@ from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 class TestWorkingTreeFormat(TestCase):
     def setUp(self):
         super(TestWorkingTreeFormat, self).setUp()
-        self.format = SvnWorkingTreeFormat()
+        self.format = SvnWorkingTreeFormat(4)
 
     def test_get_format_desc(self):
-        self.assertEqual("Subversion Working Copy", 
+        self.assertEqual("Subversion Working Copy Version 4", 
                          self.format.get_format_description())
 
     def test_initialize(self):
diff --git a/tests/test_client.py b/tests/test_client.py
new file mode 100644 (file)
index 0000000..cd63813
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Subversion client library tests."""
+
+from bzrlib.tests import TestCase
+from bzrlib.plugins.svn import client
+from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
+
+class TestClient(TestCaseWithSubversionRepository):
+    def setUp(self):
+        super(TestClient, self).setUp()
+        self.repos_url = self.make_client("d", "dc")
+        self.client = client.Client()
+
+    def test_add(self):
+        self.build_tree({"dc/foo": None})
+        self.client.add("dc/foo")
index 1080923589aeeaf0dbcd3422037b35ad4881479a..9a1b06834d7df45737a4b6f01d9a9a7f07a603e3 100644 (file)
@@ -32,7 +32,7 @@ import os
 from bzrlib.plugins.svn.transport import SvnRaTransport
 from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 
-from svn.core import svn_time_to_cstring
+from bzrlib.plugins.svn.core import time_to_cstring
 
 class TestNativeCommit(TestCaseWithSubversionRepository):
     def test_simple_commit(self):
@@ -154,10 +154,10 @@ class TestNativeCommit(TestCaseWithSubversionRepository):
         wt.rename_one("foo", "bar")
         wt.commit(message="doe")
         paths = self.client_log("dc", 2, 0)[2][0]
-        self.assertEquals('D', paths["/foo"].action)
-        self.assertEquals('A', paths["/bar"].action)
-        self.assertEquals('/foo', paths["/bar"].copyfrom_path)
-        self.assertEquals(1, paths["/bar"].copyfrom_rev)
+        self.assertEquals('D', paths["/foo"][0])
+        self.assertEquals('A', paths["/bar"][0])
+        self.assertEquals('/foo', paths["/bar"][1])
+        self.assertEquals(1, paths["/bar"][2])
         self.assertEquals("bar\t%s\n" % oldid, 
                           self.client_get_prop(repos_url, "bzr:file-ids", 2))
 
@@ -172,10 +172,10 @@ class TestNativeCommit(TestCaseWithSubversionRepository):
         self.assertTrue(wt.has_filename("bar"))
         wt.commit(message="doe")
         paths = self.client_log("dc", 2, 0)[2][0]
-        self.assertEquals('D', paths["/adir/foo"].action)
-        self.assertEquals('A', paths["/bar"].action)
-        self.assertEquals('/adir/foo', paths["/bar"].copyfrom_path)
-        self.assertEquals(1, paths["/bar"].copyfrom_rev)
+        self.assertEquals('D', paths["/adir/foo"][0])
+        self.assertEquals('A', paths["/bar"][0])
+        self.assertEquals('/adir/foo', paths["/bar"][1])
+        self.assertEquals(1, paths["/bar"][2])
 
     def test_commit_revision_id(self):
         repos_url = self.make_client('d', 'dc')
@@ -416,10 +416,10 @@ class TestPush(TestCaseWithSubversionRepository):
         wt.commit(message="doe")
         self.olddir.open_branch().pull(self.newdir.open_branch())
         paths = self.client_log(self.repos_url, 3, 0)[3][0]
-        self.assertEquals('D', paths["/vla"].action)
-        self.assertEquals('A', paths["/bar"].action)
-        self.assertEquals('/vla', paths["/bar"].copyfrom_path)
-        self.assertEquals(2, paths["/bar"].copyfrom_rev)
+        self.assertEquals('D', paths["/vla"][0])
+        self.assertEquals('A', paths["/bar"][0])
+        self.assertEquals('/vla', paths["/bar"][1])
+        self.assertEquals(2, paths["/bar"][2])
 
     def test_commit_rename_file_from_directory(self):
         wt = self.newdir.open_workingtree()
@@ -434,10 +434,10 @@ class TestPush(TestCaseWithSubversionRepository):
         self.olddir.open_branch().pull(self.newdir.open_branch())
         paths = self.client_log(self.repos_url, 3, 0)[3][0]
         mutter('paths %r' % paths)
-        self.assertEquals('D', paths["/adir/foo"].action)
-        self.assertEquals('A', paths["/bar"].action)
-        self.assertEquals('/adir/foo', paths["/bar"].copyfrom_path)
-        self.assertEquals(2, paths["/bar"].copyfrom_rev)
+        self.assertEquals('D', paths["/adir/foo"][0])
+        self.assertEquals('A', paths["/bar"][0])
+        self.assertEquals('/adir/foo', paths["/bar"][1])
+        self.assertEquals(2, paths["/bar"][2])
 
     def test_commit_remove(self):
         wt = self.newdir.open_workingtree()
@@ -449,7 +449,7 @@ class TestPush(TestCaseWithSubversionRepository):
         self.olddir.open_branch().pull(self.newdir.open_branch())
         paths = self.client_log(self.repos_url, 3, 0)[3][0]
         mutter('paths %r' % paths)
-        self.assertEquals('D', paths["/foob"].action)
+        self.assertEquals('D', paths["/foob"][0])
 
     def test_commit_rename_remove_parent(self):
         wt = self.newdir.open_workingtree()
@@ -463,10 +463,10 @@ class TestPush(TestCaseWithSubversionRepository):
         self.olddir.open_branch().pull(self.newdir.open_branch())
         paths = self.client_log(self.repos_url, 3, 0)[3][0]
         mutter('paths %r' % paths)
-        self.assertEquals('D', paths["/adir"].action)
-        self.assertEquals('A', paths["/bar"].action)
-        self.assertEquals('/adir/foob', paths["/bar"].copyfrom_path)
-        self.assertEquals(2, paths["/bar"].copyfrom_rev)
+        self.assertEquals('D', paths["/adir"][0])
+        self.assertEquals('A', paths["/bar"][0])
+        self.assertEquals('/adir/foob', paths["/bar"][1])
+        self.assertEquals(2, paths["/bar"][2])
 
     def test_commit_remove_nested(self):
         wt = self.newdir.open_workingtree()
@@ -479,7 +479,7 @@ class TestPush(TestCaseWithSubversionRepository):
         self.olddir.open_branch().pull(self.newdir.open_branch())
         paths = self.client_log(self.repos_url, 3, 0)[3][0]
         mutter('paths %r' % paths)
-        self.assertEquals('D', paths["/adir/foob"].action)
+        self.assertEquals('D', paths["/adir/foob"][0])
 
 
 class TestPushNested(TestCaseWithSubversionRepository):
@@ -577,7 +577,7 @@ class HeavyWeightCheckoutTests(TestCaseWithSubversionRepository):
 
 class RevpropTests(TestCaseWithSubversionRepository):
     def test_change_revprops(self):
-        repos_url = self.make_repository("d")
+        repos_url = self.make_repository("d", allow_revprop_changes=True)
 
         dc = self.commit_editor(repos_url, message="My commit")
         dc.add_file("foo.txt")
@@ -585,10 +585,12 @@ class RevpropTests(TestCaseWithSubversionRepository):
 
         transport = SvnRaTransport(repos_url)
         set_svn_revprops(transport, 1, {"svn:author": "Somebody", 
-                                        "svn:date": svn_time_to_cstring(1000000*473385600)})
+                                        "svn:date": time_to_cstring(1000000*473385600)})
+
+       self.assertEquals(1, transport.get_latest_revnum())
 
         self.assertEquals(("Somebody", "1985-01-01T00:00:00.000000Z", "My commit"), 
-                          self.client_log(repos_url)[1][1:])
+                self.client_log(repos_url)[1][1:])
 
     def test_change_revprops_disallowed(self):
         repos_url = self.make_repository("d", allow_revprop_changes=False)
@@ -599,7 +601,7 @@ class RevpropTests(TestCaseWithSubversionRepository):
 
         transport = SvnRaTransport(repos_url)
         self.assertRaises(RevpropChangeFailed, 
-                lambda: set_svn_revprops(transport, 1, {"svn:author": "Somebody", "svn:date": svn_time_to_cstring(1000000*473385600)}))
+                lambda: set_svn_revprops(transport, 1, {"svn:author": "Somebody", "svn:date": time_to_cstring(1000000*473385600)}))
 
 
 class SvkTestCase(TestCase):
index 3154687331bc0121661f26b688f24c22403483e0..4d0030aa7e2c4a3ccc7631148f28f52c0b29794b 100644 (file)
@@ -31,7 +31,7 @@ from bzrlib.plugins.svn.mapping3 import set_branching_scheme
 from bzrlib.plugins.svn.mapping3.scheme import TrunkBranchingScheme, NoBranchingScheme
 from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 
-import svn.repos
+from bzrlib.plugins.svn import repos
 
 class TestLoadDumpfile(TestCaseInTempDir):
     def test_loaddumpfile(self):
@@ -52,10 +52,9 @@ V 27
 PROPS-END
 """)
         load_dumpfile(dumpfile, "d")
-        repos = svn.repos.open("d")
-        fs = svn.repos.fs(repos)
+        r = repos.Repository("d")
         self.assertEqual("6987ef2d-cd6b-461f-9991-6f1abef3bd59", 
-                svn.fs.get_uuid(fs))
+                r.fs().get_uuid())
 
     def test_loaddumpfile_invalid(self):
         dumpfile = os.path.join(self.test_dir, "dumpfile")
diff --git a/tests/test_core.py b/tests/test_core.py
new file mode 100644 (file)
index 0000000..5b2a668
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Subversion core library tests."""
+
+from bzrlib.tests import TestCase
+from bzrlib.plugins.svn import core
+
+class TestCore(TestCase):
+    def setUp(self):
+        super(TestCore, self).setUp()
+
+    def test_exc(self):
+        self.assertIsInstance(core.SubversionException("foo", 1), Exception)
+
+    def test_get_config(self):
+        self.assertIsInstance(core.get_config(), dict)
+
+    def test_time_from_cstring(self):
+        self.assertEquals(1225704780716938L, core.time_from_cstring("2008-11-03T09:33:00.716938Z"))
+
+    def test_time_to_cstring(self):
+        self.assertEquals("2008-11-03T09:33:00.716938Z", core.time_to_cstring(1225704780716938L))
+
index 51d96412d1ca672e64ec4489b1aa318e3b1dc170..39d4925c51578c334ed93c38f9adca103b5ea1e9 100644 (file)
@@ -19,10 +19,9 @@ from bzrlib.errors import (ConnectionError, ConnectionReset, LockError,
                            UnexpectedEndOfContainerError)
 from bzrlib.tests import TestCase
 
+from bzrlib.plugins.svn.core import SubversionException
 from bzrlib.plugins.svn.errors import *
 
-import svn.core
-from svn.core import SubversionException
 
 class TestConvertError(TestCase):
     def test_decorator_unknown(self):
index 91a1ee4ec8fe5efe6a903411f7d3e8dc4466cbea..835e4d3bae1223a75a6d4f350f28973ba5960b6a 100644 (file)
@@ -1317,9 +1317,7 @@ Node-copyfrom-path: x
         self.client_add("dc/old-trunk")
         self.client_commit("dc", "trunk data")
 
-        self.build_tree({'dc/trunk': None})
-        self.client_add("dc/trunk")
-        self.client_copy("dc/old-trunk/lib", "dc/trunk")
+        self.client_copy("dc/old-trunk", "dc/trunk")
         self.client_commit("dc", "revive old trunk")
 
         oldrepos = Repository.open(repos_url)
index 5c2372fc2c08e41885257cee240ee30b9eaf8a94..c889b1d0ca9a30629351e93cb5a15c468430a0bb 100644 (file)
 
 """Log walker tests."""
 
+from bzrlib import debug
 from bzrlib.errors import NoSuchRevision
 
 import os
 from bzrlib.plugins.svn import logwalker
-from bzrlib import debug
 from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 from bzrlib.plugins.svn.transport import SvnRaTransport
 
index 4053bab73205a8ca25b46fe7da6827dfceec72f3..7d72d59733efbd1d8af52baa48b31b62fc5a937e 100644 (file)
@@ -29,10 +29,10 @@ from bzrlib.trace import mutter
 from bzrlib.workingtree import WorkingTree
 
 import os
+from time import sleep
+
 from bzrlib.plugins.svn import format
-import svn.core
 from bzrlib.plugins.svn.errors import ChangesRootLHSHistory, MissingPrefix
-from time import sleep
 from bzrlib.plugins.svn.commit import push
 from bzrlib.plugins.svn.mapping import SVN_PROP_BZR_REVISION_ID
 from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
diff --git a/tests/test_ra.py b/tests/test_ra.py
new file mode 100644 (file)
index 0000000..9a3360a
--- /dev/null
@@ -0,0 +1,115 @@
+# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Subversion ra library tests."""
+
+from bzrlib.tests import TestCase
+from bzrlib.plugins.svn import ra
+from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
+
+class VersionTest(TestCase):
+    def test_version_length(self):
+        self.assertEquals(4, len(ra.version()))
+
+
+class TestRemoteAccess(TestCaseWithSubversionRepository):
+    def setUp(self):
+        super(TestRemoteAccess, self).setUp()
+        self.repos_url = self.make_client("d", "dc")
+        self.ra = ra.RemoteAccess(self.repos_url)
+
+    def do_commit(self):
+        self.build_tree({'dc/foo': None})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "msg")
+
+    def test_repr(self):
+        self.assertEquals("RemoteAccess(%s)" % self.repos_url,
+                          repr(self.ra))
+
+    def test_latest_revnum(self):
+        self.assertEquals(0, self.ra.get_latest_revnum())
+
+    def test_latest_revnum_one(self):
+        self.do_commit()
+        self.assertEquals(1, self.ra.get_latest_revnum())
+
+    def test_get_uuid(self):
+        self.assertIsInstance(self.ra.get_uuid(), str)
+
+    def test_get_repos_root(self):
+        self.assertEqual(self.repos_url, self.ra.get_repos_root())
+
+    def test_reparent(self):
+        self.ra.reparent(self.repos_url)
+
+    def test_has_capability(self):
+        self.assertRaises(NotImplementedError, self.ra.has_capability, "FOO")
+
+    def test_get_dir(self):
+        ret = self.ra.get_dir("", 0)
+        self.assertIsInstance(ret, tuple)
+
+    def test_change_rev_prop(self):
+        self.do_commit()
+        self.ra.change_rev_prop(1, "foo", "bar")
+
+    def test_rev_proplist(self):
+        self.assertIsInstance(self.ra.rev_proplist(0), dict)
+
+    def test_get_log(self):
+        returned = []
+        def cb(*args):
+            returned.append(args)
+        def check_results(returned):
+            self.assertEquals(2, len(returned))
+            (paths, revnum, props) = returned[0]
+            self.assertEquals(None, paths)
+            self.assertEquals(revnum, 0)
+            self.assertEquals(["svn:date"], props.keys())
+            (paths, revnum, props) = returned[1]
+            self.assertEquals({'/foo': ('A', None, -1)}, paths)
+            self.assertEquals(revnum, 1)
+            self.assertEquals(set(["svn:date", "svn:author", "svn:log"]), 
+                              set(props.keys()))
+        self.ra.get_log(cb, [""], 0, 0)
+        self.assertEquals(1, len(returned))
+        self.do_commit()
+        returned = []
+        self.ra.get_log(cb, ["/"], 0, 1, discover_changed_paths=True, 
+                        strict_node_history=False)
+        check_results(returned)
+        returned = []
+        self.ra.get_log(cb, None, 0, 1, discover_changed_paths=True, 
+                        strict_node_history=False)
+        check_results(returned)
+
+    def test_get_commit_editor_busy(self):
+        def mycb(rev):
+            pass
+        editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb)
+        self.assertRaises(ra.BusyException, self.ra.get_commit_editor, {"svn:log": "foo"}, mycb)
+        editor.abort()
+
+    def test_get_commit_editor(self):
+        def mycb(paths, rev, revprops):
+            pass
+        editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb)
+        dir = editor.open_root(0)
+        subdir = dir.add_directory("foo")
+        subdir.close()
+        dir.close()
+        editor.close()
+
index 74af0d8bcdc4d7f55b26eb0bca32d3d2d2db7779..5cfb5a5d6c3ab0f2cea54c5c8a9cc8b5796b9580 100644 (file)
@@ -22,8 +22,7 @@ from bzrlib.bzrdir import BzrDir, format_registry
 from bzrlib.errors import (NoRepositoryPresent, NotBranchError, NotLocalUrl,
                            NoWorkingTree, AlreadyBranchError)
 
-import svn
-
+from bzrlib.plugins.svn import core
 from bzrlib.plugins.svn.format import SvnRemoteFormat
 from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
 from bzrlib.plugins.svn.transport import SvnRaTransport
@@ -75,7 +74,7 @@ class TestRemoteAccess(TestCaseWithSubversionRepository):
         b = x.create_branch()
         self.assertEquals(repos_url+"/trunk", b.base)
         transport = SvnRaTransport(repos_url)
-        self.assertEquals(svn.core.svn_node_dir
+        self.assertEquals(core.NODE_DIR
                 transport.check_path("trunk", 1))
 
     def test_bad_dir(self):
index 9d352c562fd8023d421e794f116cdbb520c01edb..211249b30f44136651edb9835888a42f10085d11 100644 (file)
@@ -1,7 +1,5 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2006-2007 Jelmer Vernooij <jelmer@samba.org>
-
+# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation; either version 3 of the License, or
 # GNU General Public License for more details.
 
 # You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""Subversion repository tests."""
-
-from bzrlib import urlutils
-from bzrlib.branch import Branch
-from bzrlib.bzrdir import BzrDir, format_registry
-from bzrlib.config import GlobalConfig
-from bzrlib.errors import NoSuchRevision, UninitializableFormat, BzrError
-from bzrlib.inventory import Inventory
-from bzrlib.osutils import has_symlinks
-from bzrlib.repository import Repository
-from bzrlib.revision import NULL_REVISION, Revision
-from bzrlib.tests import TestCase
-
-import os, sys
-
-import svn.fs, svn
-
-from bzrlib.plugins.svn import format
-from bzrlib.plugins.svn.mapping import (escape_svn_path, unescape_svn_path, 
-                     SVN_PROP_BZR_REVISION_ID)
-from bzrlib.plugins.svn.mapping3 import (SVN_PROP_BZR_BRANCHING_SCHEME, set_branching_scheme,
-                      set_property_scheme, BzrSvnMappingv3)
-from bzrlib.plugins.svn.mapping3.scheme import (TrunkBranchingScheme, NoBranchingScheme, 
-                    ListBranchingScheme, SingleBranchingScheme)
-from bzrlib.plugins.svn.transport import SvnRaTransport
-from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
-from bzrlib.plugins.svn.repository import SvnRepositoryFormat
-
-
-class TestSubversionRepositoryWorks(TestCaseWithSubversionRepository):
-    def test_format(self):
-        """ Test repository format is correct """
-        bzrdir = self.make_local_bzrdir('a', 'ac')
-        self.assertEqual(bzrdir._format.get_format_string(), \
-                "Subversion Local Checkout")
-        
-        self.assertEqual(bzrdir._format.get_format_description(), \
-                "Subversion Local Checkout")
-
-    def test_get_branch_log(self):
-        repos_url = self.make_repository("a")
-        cb = self.commit_editor(repos_url)
-        cb.add_file("foo")
-        cb.done()
-
-        repos = Repository.open(repos_url)
-
-        self.assertEqual([
-            ('', {'foo': ('A', None, -1)}, 1), 
-            ('', {'': ('A', None, -1)}, 0)],
-            [(l.branch_path, l.paths, l.revnum) for l in repos.iter_reverse_branch_changes("", 1, NoBranchingScheme())])
-
-    def test_make_working_trees(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        self.assertFalse(repos.make_working_trees())
-
-    def test_get_config_global_set(self):
-        repos_url = self.make_repository("a")
-        cfg = GlobalConfig()
-        cfg.set_user_option("foo", "Still Life")
-
-        repos = Repository.open(repos_url)
-        self.assertEquals("Still Life", 
-                repos.get_config().get_user_option("foo"))
-
-    def test_get_config(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        repos.get_config().set_user_option("foo", "Van Der Graaf Generator")
-
-        repos = Repository.open(repos_url)
-        self.assertEquals("Van Der Graaf Generator", 
-                repos.get_config().get_user_option("foo"))
-
-
-    def test_get_physical_lock_status(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        self.assertFalse(repos.get_physical_lock_status())
-
-    def test_iter_changes_parent_rename(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/foo/bar': None})
-        self.client_add('dc/foo')
-        self.client_commit('dc', 'a')
-        self.client_update('dc')
-        self.client_copy('dc/foo', 'dc/bla')
-        self.client_commit('dc', 'b')
-        repos = Repository.open(repos_url)
-        ret = list(repos.iter_changes('bla/bar', 2, BzrSvnMappingv3(SingleBranchingScheme('bla/bar'))))
-        self.assertEquals(1, len(ret))
-        self.assertEquals("bla/bar", ret[0][0])
-
-    def test_set_make_working_trees(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        repos.set_make_working_trees(True)
-        self.assertFalse(repos.make_working_trees())
-
-    def test_get_fileid_map(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        mapping = repos.get_mapping()
-        self.assertEqual({u"": (mapping.generate_file_id(repos.uuid, 0, "", u""), mapping.generate_revision_id(repos.uuid, 0, ""))}, repos.get_fileid_map(0, "", mapping))
-
-    def test_generate_revision_id_forced_revid(self):
-        repos_url = self.make_client("a", "dc")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
-                             "2 someid\n")
-        self.client_commit("dc", "set id")
-        repos = Repository.open(repos_url)
-        revid = repos.generate_revision_id(1, "", repos.get_mapping())
-        self.assertEquals("someid", revid)
-
-    def test_generate_revision_id_forced_revid_invalid(self):
-        repos_url = self.make_client("a", "dc")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
-                             "corrupt-id\n")
-        self.client_commit("dc", "set id")
-        repos = Repository.open(repos_url)
-        revid = repos.generate_revision_id(1, "", repos.get_mapping())
-        self.assertEquals(
-                repos.get_mapping().generate_revision_id(repos.uuid, 1, ""),
-                revid)
-
-    def test_add_revision(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        self.assertRaises(NotImplementedError, repos.add_revision, "revid", 
-                None)
-
-    def test_has_signature_for_revision_id_no(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        self.assertFalse(repos.has_signature_for_revision_id("foo"))
-
-    def test_set_signature(self):
-        repos_url = self.make_client("a", "dc")
-        repos = Repository.open(repos_url)
-        self.build_tree({"dc/foo": "bar"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "msg")
-        revid = repos.get_mapping().generate_revision_id(repos.uuid, 1, "")
-        repos.add_signature_text(revid, "TEXT")
-        self.assertTrue(repos.has_signature_for_revision_id(revid))
-        self.assertEquals(repos.get_signature_text(revid), "TEXT")
-
-    def test_repr(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-
-        repos = Repository.open(repos_url)
-
-        self.assertEqual("SvnRepository('%s/')" % urlutils.local_path_to_url(urlutils.join(self.test_dir, "a")), repos.__repr__())
-
-    def test_get_branch_invalid_revision(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        self.assertRaises(NoSuchRevision, list, 
-               repos.iter_reverse_branch_changes("/", 20, NoBranchingScheme()))
-
-    def test_follow_branch_switched_parents(self):
-        repos_url = self.make_client('a', 'dc')
-        self.build_tree({'dc/pykleur/trunk/pykleur': None})
-        self.client_add("dc/pykleur")
-        self.client_commit("dc", "initial")
-        self.build_tree({'dc/pykleur/trunk/pykleur/afile': 'contents'})
-        self.client_add("dc/pykleur/trunk/pykleur/afile")
-        self.client_commit("dc", "add file")
-        self.client_copy("dc/pykleur", "dc/pygments", 1)
-        self.client_delete('dc/pykleur')
-        self.client_update("dc")
-        self.client_commit("dc", "commit")
-        repos = Repository.open(repos_url)
-        self.assertEquals([
-            ('pygments/trunk', {'pygments': (u'A', 'pykleur', 1),
-                                'pygments/trunk': (u'R', 'pykleur/trunk', 2),
-                                'pykleur': (u'D', None, -1)}, 3),
-            ('pykleur/trunk', {'pykleur/trunk/pykleur/afile': (u'A', None, -1)}, 2),
-            ('pykleur/trunk',
-                    {'pykleur': (u'A', None, -1),
-                     'pykleur/trunk': (u'A', None, -1),
-                     'pykleur/trunk/pykleur': (u'A', None, -1)},
-             1)],
-            [(l.branch_path, l.paths, l.revnum) for l in repos.iter_reverse_branch_changes("pygments/trunk", 3, TrunkBranchingScheme(1))])
-
-    def test_follow_branch_move_single(self):
-        repos_url = self.make_client('a', 'dc')
-        self.build_tree({'dc/pykleur/bla': None})
-        self.client_add("dc/pykleur")
-        self.client_commit("dc", "initial")
-        self.client_copy("dc/pykleur", "dc/pygments", 1)
-        self.client_update("dc")
-        self.client_commit("dc", "commit")
-        repos = Repository.open(repos_url)
-        changes = repos.iter_reverse_branch_changes("pygments", 2, SingleBranchingScheme("pygments"))
-        self.assertEquals([('pygments',
-              {'pygments/bla': ('A', None, -1), 'pygments': ('A', None, -1)},
-                2)],
-                [(l.branch_path, l.paths, l.revnum) for l in changes])
-
-    def test_history_all(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/trunk/file': "data", "dc/foo/file":"data"})
-        self.client_add("dc/trunk")
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-
-        repos = Repository.open(repos_url)
-
-        self.assertEqual(2, 
-                   len(list(repos.all_revision_ids(repos.get_layout()))))
-
-    def test_all_revs_empty(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-        self.assertEqual([], list(repos.all_revision_ids()))
-
-    def test_all_revs(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/trunk/file': "data", "dc/foo/file":"data"})
-        self.client_add("dc/trunk")
-        self.client_commit("dc", "add trunk")
-        self.build_tree({'dc/branches/somebranch/somefile': 'data'})
-        self.client_add("dc/branches")
-        self.client_commit("dc", "add a branch")
-        self.client_delete("dc/branches/somebranch")
-        self.client_commit("dc", "remove branch")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-        mapping = repos.get_mapping()
-        self.assertEqual([
-            repos.generate_revision_id(1, "trunk", mapping), 
-            repos.generate_revision_id(2, "branches/somebranch", mapping)],
-            list(repos.all_revision_ids()))
-
-    def test_follow_history_empty(self):
-        repos_url = self.make_repository("a")
-        repos = Repository.open(repos_url)
-        self.assertEqual([repos.generate_revision_id(0, '', repos.get_mapping())], 
-              list(repos.all_revision_ids(repos.get_layout())))
-
-    def test_follow_history_empty_branch(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/trunk/afile': "data", "dc/branches": None})
-        self.client_add("dc/trunk")
-        self.client_add("dc/branches")
-        self.client_commit("dc", "My Message")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-        self.assertEqual([repos.generate_revision_id(1, 'trunk', repos.get_mapping())], 
-                list(repos.all_revision_ids(repos.get_layout())))
-
-    def test_follow_history_follow(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/trunk/afile': "data", "dc/branches": None})
-        self.client_add("dc/trunk")
-        self.client_add("dc/branches")
-        self.client_commit("dc", "My Message")
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-        self.client_copy("dc/trunk", "dc/branches/abranch")
-        self.client_commit("dc", "Create branch")
+"""Subversion rpeository library tests."""
 
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
+import os
+from bzrlib.plugins.svn import repos
+from bzrlib.tests import TestCaseInTempDir
 
-        items = list(repos.all_revision_ids(repos.get_layout()))
-        self.assertEqual([repos.generate_revision_id(1, 'trunk', repos.get_mapping()),
-                          repos.generate_revision_id(2, 'branches/abranch', repos.get_mapping())
-                          ], items)
-
-    def test_branch_log_specific(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({
-            'dc/branches': None,
-            'dc/branches/brancha': None,
-            'dc/branches/branchab': None,
-            'dc/branches/brancha/data': "data", 
-            "dc/branches/branchab/data":"data"})
-        self.client_add("dc/branches")
-        self.client_commit("dc", "My Message")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.assertEqual(1, len(list(repos.iter_reverse_branch_changes("branches/brancha",
-            1, TrunkBranchingScheme()))))
-
-    def test_branch_log_specific_ignore(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/branches': None})
-        self.client_add("dc/branches")
-        self.build_tree({
-            'dc/branches/brancha': None,
-            'dc/branches/branchab': None,
-            'dc/branches/brancha/data': "data", 
-            "dc/branches/branchab/data":"data"})
-        self.client_add("dc/branches/brancha")
-        self.client_commit("dc", "My Message")
-
-        self.client_add("dc/branches/branchab")
-        self.client_commit("dc", "My Message2")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.assertEqual(1, len(list(repos.iter_reverse_branch_changes("branches/brancha",
-            2, TrunkBranchingScheme()))))
-
-    def test_find_branches(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({
-            'dc/branches/brancha': None,
-            'dc/branches/branchab': None,
-            'dc/branches/brancha/data': "data", 
-            "dc/branches/branchab/data":"data"})
-        self.client_add("dc/branches")
-        self.client_commit("dc", "My Message")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-        branches = repos.find_branches()
-        self.assertEquals(2, len(branches))
-        self.assertEquals(urlutils.join(repos.base, "branches/brancha"), 
-                          branches[1].base)
-        self.assertEquals(urlutils.join(repos.base, "branches/branchab"), 
-                          branches[0].base)
-
-    def test_find_branchpaths_moved(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({
-            'dc/tmp/branches/brancha': None,
-            'dc/tmp/branches/branchab': None,
-            'dc/tmp/branches/brancha/data': "data", 
-            "dc/tmp/branches/branchab/data":"data"})
-        self.client_add("dc/tmp")
-        self.client_commit("dc", "My Message")
-        self.client_copy("dc/tmp/branches", "dc/tags")
-        self.client_commit("dc", "My Message 2")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.assertEqual([("tags/branchab", 2, True), 
-                          ("tags/brancha", 2, True)], 
-                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=2)))
-
-    def test_find_branchpaths_start_revno(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({'dc/branches/brancha': None})
-        self.client_add("dc/branches")
-        self.client_commit("dc", "My Message")
-        self.build_tree({'dc/branches/branchb': None})
-        self.client_add("dc/branches/branchb")
-        self.client_commit("dc", "My Message 2")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.assertEqual([("branches/branchb", 2, True)],
-                list(repos.find_branchpaths(TrunkBranchingScheme(), from_revnum=2, 
-                    to_revnum=2)))
-
-    def test_find_branchpaths_file_moved_from_nobranch(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({
-            'dc/tmp/trunk': None,
-            'dc/bla/somefile': "contents"})
-        self.client_add("dc/tmp")
-        self.client_add("dc/bla")
-        self.client_commit("dc", "My Message")
-        self.client_copy("dc/bla", "dc/tmp/branches")
-        self.client_delete("dc/tmp/branches/somefile")
-        self.client_commit("dc", "My Message 2")
-
-        Repository.open(repos_url).find_branchpaths(TrunkBranchingScheme(2))
-
-    def test_find_branchpaths_deleted_from_nobranch(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({
-            'dc/tmp/trunk': None,
-            'dc/bla/somefile': "contents"})
-        self.client_add("dc/tmp")
-        self.client_add("dc/bla")
-        self.client_commit("dc", "My Message")
-        self.client_copy("dc/bla", "dc/tmp/branches")
-        self.client_delete("dc/tmp/branches/somefile")
-        self.client_commit("dc", "My Message 2")
-
-        Repository.open(repos_url).find_branchpaths(TrunkBranchingScheme(1))
-
-    def test_find_branchpaths_moved_nobranch(self):
-        repos_url = self.make_client("a", "dc")
-        self.build_tree({
-            'dc/tmp/nested/foobar': None,
-            'dc/tmp/nested/branches/brancha': None,
-            'dc/tmp/nested/branches/branchab': None,
-            'dc/tmp/nested/branches/brancha/data': "data", 
-            "dc/tmp/nested/branches/branchab/data":"data"})
-        self.client_add("dc/tmp")
-        self.client_commit("dc", "My Message")
-        self.client_copy("dc/tmp/nested", "dc/t2")
-        self.client_commit("dc", "My Message 2")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme(1))
-
-        self.assertEqual([("t2/branches/brancha", 2, True), 
-                          ("t2/branches/branchab", 2, True)], 
-                list(repos.find_branchpaths(TrunkBranchingScheme(1), to_revnum=2)))
-
-    def test_find_branchpaths_no(self):
-        repos_url = self.make_repository("a")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, NoBranchingScheme())
-
-        self.assertEqual([("", 0, True)], 
-                list(repos.find_branchpaths(NoBranchingScheme(), to_revnum=0)))
-
-    def test_find_branchpaths_no_later(self):
-        repos_url = self.make_repository("a")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, NoBranchingScheme())
-
-        self.assertEqual([("", 0, True)], 
-                list(repos.find_branchpaths(NoBranchingScheme(), to_revnum=0)))
-
-    def test_find_branchpaths_trunk_empty(self):
-        repos_url = self.make_repository("a")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.assertEqual([], 
-                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=0)))
-
-    def test_find_branchpaths_trunk_one(self):
-        repos_url = self.make_client("a", "dc")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.build_tree({'dc/trunk/foo': "data"})
-        self.client_add("dc/trunk")
-        self.client_commit("dc", "My Message")
-
-        self.assertEqual([("trunk", 1, True)], 
-                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=1)))
-
-    def test_find_branchpaths_removed(self):
-        repos_url = self.make_client("a", "dc")
-
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-
-        self.build_tree({'dc/trunk/foo': "data"})
-        self.client_add("dc/trunk")
-        self.client_commit("dc", "My Message")
-
-        self.client_delete("dc/trunk")
-        self.client_commit("dc", "remove")
-
-        self.assertEqual([("trunk", 1, True)], 
-                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=1)))
-        self.assertEqual([("trunk", 1, False)], 
-                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=2)))
-
-    def test_url(self):
-        """ Test repository URL is kept """
-        bzrdir = self.make_local_bzrdir('b', 'bc')
-        self.assertTrue(isinstance(bzrdir, BzrDir))
-
-    def test_uuid(self):
-        """ Test UUID is retrieved correctly """
-        bzrdir = self.make_local_bzrdir('c', 'cc')
-        self.assertTrue(isinstance(bzrdir, BzrDir))
-        repository = bzrdir._find_repository()
-        fs = self.open_fs('c')
-        self.assertEqual(svn.fs.get_uuid(fs), repository.uuid)
-
-    def test_get_inventory_weave(self):
-        bzrdir = self.make_client_and_bzrdir('d', 'dc')
-        repository = bzrdir.find_repository()
-        self.assertRaises(NotImplementedError, repository.get_inventory_weave)
-
-    def test_has_revision(self):
-        bzrdir = self.make_client_and_bzrdir('d', 'dc')
-        repository = bzrdir.find_repository()
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.assertTrue(repository.has_revision(
-            repository.generate_revision_id(1, "", repository.get_mapping())))
-        self.assertFalse(repository.has_revision("some-other-revision"))
-
-    def test_has_revision_none(self):
-        bzrdir = self.make_client_and_bzrdir('d', 'dc')
-        repository = bzrdir.find_repository()
-        self.assertTrue(repository.has_revision(None))
-
-    def test_has_revision_future(self):
-        bzrdir = self.make_client_and_bzrdir('d', 'dc')
-        repository = bzrdir.find_repository()
-        self.assertFalse(repository.has_revision(
-            repository.get_mapping().generate_revision_id(repository.uuid, 5, "")))
-
-    def test_get_parent_map(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.build_tree({'dc/foo': "data2"})
-        self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        revid = repository.generate_revision_id(0, "", mapping)
-        self.assertEqual({revid: (NULL_REVISION,)}, repository.get_parent_map([revid]))
-        revid = repository.generate_revision_id(1, "", mapping)
-        self.assertEqual({revid: (repository.generate_revision_id(0, "", mapping),)}, repository.get_parent_map([revid]))
-        revid = repository.generate_revision_id(2, "", mapping)
-        self.assertEqual({revid: (repository.generate_revision_id(1, "", mapping),)},
-            repository.get_parent_map([revid]))
-        self.assertEqual({}, repository.get_parent_map(["notexisting"]))
-
-    def test_revision_fileidmap(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_set_prop("dc", "bzr:revision-info", "")
-        self.client_set_prop("dc", "bzr:file-ids", "foo\tsomeid\n")
-        self.client_commit("dc", "My Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        tree = repository.revision_tree(Branch.open(repos_url).last_revision())
-        self.assertEqual("someid", tree.inventory.path2id("foo"))
-        self.assertFalse("1@%s::foo" % repository.uuid in tree.inventory)
-
-    def test_revision_ghost_parents(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.client_update("dc")
-        self.build_tree({'dc/foo': "data2"})
-        self.client_set_prop("dc", "bzr:ancestry:v3-none", "ghostparent\n")
-        self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual((),
-                repository.get_revision(
-                    repository.generate_revision_id(0, "", mapping)).parent_ids)
-        self.assertEqual((repository.generate_revision_id(0, "", mapping),),
-                repository.get_revision(
-                    repository.generate_revision_id(1, "", mapping)).parent_ids)
-        self.assertEqual((repository.generate_revision_id(1, "", mapping),
-            "ghostparent"), 
-                repository.get_revision(
-                    repository.generate_revision_id(2, "", mapping)).parent_ids)
-    def test_revision_svk_parent(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/trunk/foo': "data", 'dc/branches/foo': None})
-        self.client_add("dc/trunk")
-        self.client_add("dc/branches")
-        self.client_commit("dc", "My Message")
-        self.client_update("dc")
-        self.build_tree({'dc/trunk/foo': "data2"})
-        repository = Repository.open("svn+%s" % repos_url)
-        set_branching_scheme(repository, TrunkBranchingScheme())
-        self.client_set_prop("dc/trunk", "svk:merge", 
-            "%s:/branches/foo:1\n" % repository.uuid)
-        self.client_commit("dc", "Second Message")
-        mapping = repository.get_mapping()
-        self.assertEqual((repository.generate_revision_id(1, "trunk", mapping),
-            repository.generate_revision_id(1, "branches/foo", mapping)), 
-                repository.get_revision(
-                    repository.generate_revision_id(2, "trunk", mapping)).parent_ids)
-    
-    def test_get_revision(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertRaises(NoSuchRevision, repository.get_revision, 
-                "nonexisting")
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.client_update("dc")
-        self.build_tree({'dc/foo': "data2"})
-        (num, date, author) = self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        rev = repository.get_revision(
-            repository.generate_revision_id(2, "", mapping))
-        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
-                rev.parent_ids)
-        self.assertEqual(rev.revision_id, 
-                repository.generate_revision_id(2, "", mapping))
-        self.assertEqual(author, rev.committer)
-        self.assertIsInstance(rev.properties, dict)
-
-    def test_get_revision_id_overriden(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertRaises(NoSuchRevision, repository.get_revision, "nonexisting")
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.build_tree({'dc/foo': "data2"})
-        self.client_set_prop("dc", "bzr:revision-id:v3-none", 
-                            "3 myrevid\n")
-        self.client_update("dc")
-        (num, date, author) = self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        revid = mapping.generate_revision_id(repository.uuid, 2, "")
-        rev = repository.get_revision("myrevid")
-        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
-                rev.parent_ids)
-        self.assertEqual(rev.revision_id, 
-                         repository.generate_revision_id(2, "", mapping))
-        self.assertEqual(author, rev.committer)
-        self.assertIsInstance(rev.properties, dict)
-
-    def test_get_revision_zero(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        rev = repository.get_revision(
-            repository.generate_revision_id(0, "", mapping))
-        self.assertEqual(repository.generate_revision_id(0, "", mapping), 
-                         rev.revision_id)
-        self.assertEqual("", rev.committer)
-        self.assertEqual({}, rev.properties)
-        self.assertEqual(None, rev.timezone)
-
-    def test_store_branching_scheme(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open(repos_url)
-        set_branching_scheme(repository, TrunkBranchingScheme(42))
-        repository = Repository.open(repos_url)
-        self.assertEquals("trunk42", str(repository.get_mapping().scheme))
-
-    def test_get_ancestry(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertRaises(NoSuchRevision, repository.get_revision, "nonexisting")
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.client_update("dc")
-        self.build_tree({'dc/foo': "data2"})
-        self.client_commit("dc", "Second Message")
-        self.client_update("dc")
-        self.build_tree({'dc/foo': "data3"})
-        self.client_commit("dc", "Third Message")
-        self.client_update("dc")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual([None, 
-            repository.generate_revision_id(0, "", mapping),
-            repository.generate_revision_id(1, "", mapping),
-            repository.generate_revision_id(2, "", mapping),
-            repository.generate_revision_id(3, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(3, "", mapping)))
-        self.assertEqual([None, 
-            repository.generate_revision_id(0, "", mapping),
-            repository.generate_revision_id(1, "", mapping),
-            repository.generate_revision_id(2, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(2, "", mapping)))
-        self.assertEqual([None,
-                    repository.generate_revision_id(0, "", mapping),
-                    repository.generate_revision_id(1, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(1, "", mapping)))
-        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(0, "", mapping)))
-        self.assertEqual([None], repository.get_ancestry(NULL_REVISION))
-
-    def test_get_ancestry2(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.build_tree({'dc/foo': "data2"})
-        self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(0, "", mapping)))
-        self.assertEqual([None, repository.generate_revision_id(0, "", mapping),
-            repository.generate_revision_id(1, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(1, "", mapping)))
-        self.assertEqual([None, 
-            repository.generate_revision_id(0, "", mapping),
-            repository.generate_revision_id(1, "", mapping),
-            repository.generate_revision_id(2, "", mapping)], 
-                repository.get_ancestry(
-                    repository.generate_revision_id(2, "", mapping)))
-
-    def test_get_ancestry_merged(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.client_update("dc")
-        self.client_set_prop("dc", "bzr:ancestry:v3-none", "a-parent\n")
-        self.build_tree({'dc/foo': "data2"})
-        self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(0, "", mapping)))
-        self.assertEqual([None, repository.generate_revision_id(0, "", mapping),
-            repository.generate_revision_id(1, "", mapping)],
-                repository.get_ancestry(
-                    repository.generate_revision_id(1, "", mapping)))
-        self.assertEqual([None, 
-            repository.generate_revision_id(0, "", mapping), "a-parent", 
-            repository.generate_revision_id(1, "", mapping), 
-                  repository.generate_revision_id(2, "", mapping)], 
-                repository.get_ancestry(
-                    repository.generate_revision_id(2, "", mapping)))
-
-    def test_get_inventory(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertRaises(NoSuchRevision, repository.get_inventory, 
-                "nonexisting")
-        self.build_tree({'dc/foo': "data", 'dc/blah': "other data"})
-        self.client_add("dc/foo")
-        self.client_add("dc/blah")
-        self.client_commit("dc", "My Message") #1
-        self.client_update("dc")
-        self.build_tree({'dc/foo': "data2", "dc/bar/foo": "data3"})
-        self.client_add("dc/bar")
-        self.client_commit("dc", "Second Message") #2
-        self.client_update("dc")
-        self.build_tree({'dc/foo': "data3"})
-        self.client_commit("dc", "Third Message") #3
-        self.client_update("dc")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        inv = repository.get_inventory(
-                repository.generate_revision_id(1, "", mapping))
-        self.assertIsInstance(inv, Inventory)
-        self.assertIsInstance(inv.path2id("foo"), basestring)
-        inv = repository.get_inventory(
-            repository.generate_revision_id(2, "", mapping))
-        self.assertEqual(repository.generate_revision_id(2, "", mapping), 
-                         inv[inv.path2id("foo")].revision)
-        self.assertEqual(repository.generate_revision_id(1, "", mapping), 
-                         inv[inv.path2id("blah")].revision)
-        self.assertIsInstance(inv, Inventory)
-        self.assertIsInstance(inv.path2id("foo"), basestring)
-        self.assertIsInstance(inv.path2id("bar"), basestring)
-        self.assertIsInstance(inv.path2id("bar/foo"), basestring)
-
-    def test_generate_revision_id(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/bla/bloe': None})
-        self.client_add("dc/bla")
-        self.client_commit("dc", "bla")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual(
-               mapping.generate_revision_id(repository.uuid, 1, "bla/bloe"), 
-            repository.generate_revision_id(1, "bla/bloe", mapping))
-
-    def test_generate_revision_id_zero(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual(mapping.generate_revision_id(repository.uuid, 0, ""), 
-                repository.generate_revision_id(0, "", mapping))
-
-    def test_lookup_revision_id(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/bloe': None})
-        self.client_add("dc/bloe")
-        self.client_commit("dc", "foobar")
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertRaises(NoSuchRevision, repository.lookup_revision_id, 
-            "nonexisting")
-        mapping = repository.get_mapping()
-        self.assertEqual(("bloe", 1), 
-            repository.lookup_revision_id(
-                repository.generate_revision_id(1, "bloe", mapping))[:2])
-
-    def test_lookup_revision_id_overridden(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/bloe': None})
-        self.client_add("dc/bloe")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", "2 myid\n")
-        self.client_commit("dc", "foobar")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual(("", 1), repository.lookup_revision_id( 
-            mapping.generate_revision_id(repository.uuid, 1, ""))[:2])
-        self.assertEqual(("", 1), 
-                repository.lookup_revision_id("myid")[:2])
-
-    def test_lookup_revision_id_overridden_invalid(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/bloe': None})
-        self.client_add("dc/bloe")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
-                             "corrupt-entry\n")
-        self.client_commit("dc", "foobar")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual(("", 1), repository.lookup_revision_id( 
-            mapping.generate_revision_id(repository.uuid, 1, ""))[:2])
-        self.assertRaises(NoSuchRevision, repository.lookup_revision_id, 
-            "corrupt-entry")
-
-    def test_lookup_revision_id_overridden_invalid_dup(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/bloe': None})
-        self.client_add("dc/bloe")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
-                             "corrupt-entry\n")
-        self.client_commit("dc", "foobar")
-        self.build_tree({'dc/bla': None})
-        self.client_add("dc/bla")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
-                "corrupt-entry\n2 corrupt-entry\n")
-        self.client_commit("dc", "foobar")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertEqual(("", 2), repository.lookup_revision_id( 
-            mapping.generate_revision_id(repository.uuid, 2, ""))[:2])
-        self.assertEqual(("", 1), repository.lookup_revision_id( 
-            mapping.generate_revision_id(repository.uuid, 1, ""))[:2])
-        self.assertEqual(("", 2), repository.lookup_revision_id( 
-            "corrupt-entry")[:2])
-
-    def test_lookup_revision_id_overridden_not_found(self):
-        """Make sure a revision id that is looked up but doesn't exist 
-        doesn't accidently end up in the revid cache."""
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/bloe': None})
-        self.client_add("dc/bloe")
-        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", "2 myid\n")
-        self.client_commit("dc", "foobar")
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertRaises(NoSuchRevision, 
-                repository.lookup_revision_id, "foobar")
-
-    def test_set_branching_scheme_property(self):
-        repos_url = self.make_client('d', 'dc')
-        self.client_set_prop("dc", SVN_PROP_BZR_BRANCHING_SCHEME, 
-            "trunk\nbranches/*\nbranches/tmp/*")
-        self.client_commit("dc", "set scheme")
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertEquals(ListBranchingScheme(["trunk", "branches/*", "branches/tmp/*"]).branch_list,
-                          repository.get_mapping().scheme.branch_list)
-
-    def test_set_property_scheme(self):
-        repos_url = self.make_client('d', 'dc')
-        repos = Repository.open(repos_url)
-        set_property_scheme(repos, ListBranchingScheme(["bla/*"]))
-        self.client_update("dc")
-        self.assertEquals("bla/*\n", 
-                   self.client_get_prop("dc", SVN_PROP_BZR_BRANCHING_SCHEME))
-        self.assertEquals("Updating branching scheme for Bazaar.", 
-                self.client_log("dc", 1, 1)[1][3])
-
-    def test_lookup_revision_id_invalid_uuid(self):
-        repos_url = self.make_client('d', 'dc')
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        self.assertRaises(NoSuchRevision, 
-            repository.lookup_revision_id, 
-                mapping.generate_revision_id("invaliduuid", 0, ""))
-        
-    def test_check(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-        repository.check([
-            repository.generate_revision_id(0, "", mapping), 
-            repository.generate_revision_id(1, "", mapping)])
-
-    def test_copy_contents_into(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo/bla': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.build_tree({'dc/foo/blo': "data2", "dc/bar/foo": "data3", 'dc/foo/bla': "data"})
-        self.client_add("dc/foo/blo")
-        self.client_add("dc/bar")
-        self.client_commit("dc", "Second Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        mapping = repository.get_mapping()
-
-        to_bzrdir = BzrDir.create("e", format.get_rich_root_format())
-        to_repos = to_bzrdir.create_repository()
-
-        repository.copy_content_into(to_repos, 
-                repository.generate_revision_id(2, "", mapping))
-
-        self.assertTrue(repository.has_revision(
-            repository.generate_revision_id(2, "", mapping)))
-        self.assertTrue(repository.has_revision(
-            repository.generate_revision_id(1, "", mapping)))
-
-    def test_is_shared(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo/bla': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        repository = Repository.open("svn+%s" % repos_url)
-        self.assertTrue(repository.is_shared())
-
-    def test_fetch_property_change_only_trunk(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/trunk/bla': "data"})
-        self.client_add("dc/trunk")
-        self.client_commit("dc", "My Message")
-        self.client_set_prop("dc/trunk", "some:property", "some data\n")
-        self.client_commit("dc", "My 3")
-        self.client_set_prop("dc/trunk", "some2:property", "some data\n")
-        self.client_commit("dc", "My 2")
-        self.client_set_prop("dc/trunk", "some:property", "some other data\n")
-        self.client_commit("dc", "My 4")
-        oldrepos = Repository.open("svn+"+repos_url)
-        self.assertEquals([('trunk', {'trunk': (u'M', None, -1)}, 3), 
-                           ('trunk', {'trunk': (u'M', None, -1)}, 2), 
-                           ('trunk', {'trunk/bla': (u'A', None, -1), 'trunk': (u'A', None, -1)}, 1)], 
-                   [(l.branch_path, l.paths, l.revnum) for l in oldrepos.iter_reverse_branch_changes("trunk", 3, TrunkBranchingScheme())])
-
-    def test_control_code_msg(self):
-        repos_url = self.make_client('d', 'dc')
-
-        self.build_tree({'dc/trunk': None})
-        self.client_add("dc/trunk")
-        self.client_commit("dc", "\x24")
-
-        self.build_tree({'dc/trunk/hosts': 'hej2'})
-        self.client_add("dc/trunk/hosts")
-        self.client_commit("dc", "bla\xfcbla") #2
-
-        self.build_tree({'dc/trunk/hosts': 'hej3'})
-        self.client_commit("dc", "a\x0cb") #3
-
-        self.build_tree({'dc/branches/foobranch/file': 'foohosts'})
-        self.client_add("dc/branches")
-        self.client_commit("dc", "foohosts") #4
-
-        oldrepos = Repository.open("svn+"+repos_url)
-        set_branching_scheme(oldrepos, TrunkBranchingScheme())
-        dir = BzrDir.create("f",format=format.get_rich_root_format())
-        newrepos = dir.create_repository()
-        oldrepos.copy_content_into(newrepos)
-
-        mapping = oldrepos.get_mapping()
-
-        self.assertTrue(newrepos.has_revision(
-            oldrepos.generate_revision_id(1, "trunk", mapping)))
-        self.assertTrue(newrepos.has_revision(
-            oldrepos.generate_revision_id(2, "trunk", mapping)))
-        self.assertTrue(newrepos.has_revision(
-            oldrepos.generate_revision_id(3, "trunk", mapping)))
-        self.assertTrue(newrepos.has_revision(
-            oldrepos.generate_revision_id(4, "branches/foobranch", mapping)))
-        self.assertFalse(newrepos.has_revision(
-            oldrepos.generate_revision_id(4, "trunk", mapping)))
-        self.assertFalse(newrepos.has_revision(
-            oldrepos.generate_revision_id(2, "", mapping)))
-
-        rev = newrepos.get_revision(oldrepos.generate_revision_id(1, "trunk", mapping))
-        self.assertEqual("$", rev.message)
-
-        rev = newrepos.get_revision(
-            oldrepos.generate_revision_id(2, "trunk", mapping))
-        self.assertEqual('bla\xc3\xbcbla', rev.message.encode("utf-8"))
-
-        rev = newrepos.get_revision(oldrepos.generate_revision_id(3, "trunk", mapping))
-        self.assertEqual(u"a\\x0cb", rev.message)
-
-    def test_set_branching_scheme(self):
-        repos_url = self.make_client('d', 'dc')
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, NoBranchingScheme())
-
-    def testlhs_revision_parent_none(self):
-        repos_url = self.make_client('d', 'dc')
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, NoBranchingScheme())
-        self.assertEquals(NULL_REVISION, repos.lhs_revision_parent("", 0, NoBranchingScheme()))
-
-    def testlhs_revision_parent_first(self):
-        repos_url = self.make_client('d', 'dc')
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, NoBranchingScheme())
-        self.build_tree({'dc/adir/afile': "data"})
-        self.client_add("dc/adir")
-        self.client_commit("dc", "Initial commit")
-        mapping = repos.get_mapping()
-        self.assertEquals(repos.generate_revision_id(0, "", mapping), \
-                repos.lhs_revision_parent("", 1, mapping))
-
-    def testlhs_revision_parent_simple(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/trunk/adir/afile': "data", 
-                         'dc/trunk/adir/stationary': None,
-                         'dc/branches/abranch': None})
-        self.client_add("dc/trunk")
-        self.client_add("dc/branches")
-        self.client_commit("dc", "Initial commit")
-        self.build_tree({'dc/trunk/adir/afile': "bla"})
-        self.client_commit("dc", "Incremental commit")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme())
-        mapping = repos.get_mapping()
-        self.assertEquals(repos.generate_revision_id(1, "trunk", mapping), \
-                repos.lhs_revision_parent("trunk", 2, mapping))
-
-    def testlhs_revision_parent_copied(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/py/trunk/adir/afile': "data", 
-                         'dc/py/trunk/adir/stationary': None})
-        self.client_add("dc/py")
-        self.client_commit("dc", "Initial commit")
-        self.client_copy("dc/py", "dc/de")
-        self.client_commit("dc", "Incremental commit")
-        self.build_tree({'dc/de/trunk/adir/afile': "bla"})
-        self.client_commit("dc", "Change de")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme(1))
-        mapping = repos.get_mapping()
-        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
-                repos.lhs_revision_parent("de/trunk", 3, mapping))
-
-    def test_mainline_revision_copied(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/py/trunk/adir/afile': "data", 
-                         'dc/py/trunk/adir/stationary': None})
-        self.client_add("dc/py")
-        self.client_commit("dc", "Initial commit")
-        self.build_tree({'dc/de':None})
-        self.client_add("dc/de")
-        self.client_copy("dc/py/trunk", "dc/de/trunk")
-        self.client_commit("dc", "Copy trunk")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme(1))
-        mapping = repos.get_mapping()
-        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
-                repos.lhs_revision_parent("de/trunk", 2, mapping))
-
-    def test_mainline_revision_nested_deleted(self):
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/py/trunk/adir/afile': "data", 
-                         'dc/py/trunk/adir/stationary': None})
-        self.client_add("dc/py")
-        self.client_commit("dc", "Initial commit")
-        self.client_copy("dc/py", "dc/de")
-        self.client_commit("dc", "Incremental commit")
-        self.client_delete("dc/de/trunk/adir")
-        self.client_commit("dc", "Another incremental commit")
-        repos = Repository.open(repos_url)
-        set_branching_scheme(repos, TrunkBranchingScheme(1))
-        mapping = repos.get_mapping()
-        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
-                repos.lhs_revision_parent("de/trunk", 3, mapping))
-
-    def test_mainline_revision_missing(self):
-        repos_url = self.make_client('d', 'dc')
-        repos = Repository.open(repos_url)
-        self.build_tree({'dc/py/trunk/adir/afile': "data", 
-                         'dc/py/trunk/adir/stationary': None})
-        self.client_add("dc/py")
-        self.client_commit("dc", "Initial commit")
-        self.assertRaises(NoSuchRevision, 
-                lambda: repos.lhs_revision_parent("trunk", 2, repos.get_mapping()))
-
-
-class TestSvnRevisionTree(TestCaseWithSubversionRepository):
-    def setUp(self):
-        super(TestSvnRevisionTree, self).setUp()
-        repos_url = self.make_client('d', 'dc')
-        self.build_tree({'dc/foo/bla': "data"})
-        self.client_add("dc/foo")
-        self.client_commit("dc", "My Message")
-        self.repos = Repository.open(repos_url)
-        mapping = self.repos.get_mapping()
-        self.inventory = self.repos.get_inventory(
-                self.repos.generate_revision_id(1, "", mapping))
-        self.tree = self.repos.revision_tree(
-                self.repos.generate_revision_id(1, "", mapping))
-
-    def test_inventory(self):
-        self.assertIsInstance(self.tree.inventory, Inventory)
-        self.assertEqual(self.inventory, self.tree.inventory)
-
-    def test_get_parent_ids(self):
-        mapping = self.repos.get_mapping()
-        self.assertEqual((self.repos.generate_revision_id(0, "", mapping),), self.tree.get_parent_ids())
-
-    def test_get_parent_ids_zero(self):
-        mapping = self.repos.get_mapping()
-        tree = self.repos.revision_tree(
-                self.repos.generate_revision_id(0, "", mapping))
-        self.assertEqual((), tree.get_parent_ids())
-
-    def test_get_revision_id(self):
-        mapping = self.repos.get_mapping()
-        self.assertEqual(self.repos.generate_revision_id(1, "", mapping),
-                         self.tree.get_revision_id())
-
-    def test_get_file_lines(self):
-        self.assertEqual(["data"], 
-                self.tree.get_file_lines(self.inventory.path2id("foo/bla")))
-
-    def test_executable(self):
-        self.client_set_prop("dc/foo/bla", "svn:executable", "*")
-        self.client_commit("dc", "My Message")
-
-        mapping = self.repos.get_mapping()
-        
-        inventory = self.repos.get_inventory(
-                self.repos.generate_revision_id(2, "", mapping))
-
-        self.assertTrue(inventory[inventory.path2id("foo/bla")].executable)
-
-    def test_symlink(self):
-        if not has_symlinks():
-            return
-        os.symlink('foo/bla', 'dc/bar')
-        self.client_add('dc/bar')
-        self.client_commit("dc", "My Message")
-
-        mapping = self.repos.get_mapping()
-        
-        inventory = self.repos.get_inventory(
-                self.repos.generate_revision_id(2, "", mapping))
-
-        self.assertEqual('symlink', inventory[inventory.path2id("bar")].kind)
-        self.assertEqual('foo/bla', 
-                inventory[inventory.path2id("bar")].symlink_target)
-
-    def test_not_executable(self):
-        self.assertFalse(self.inventory[
-            self.inventory.path2id("foo/bla")].executable)
-
-
-class EscapeTest(TestCase):
-    def test_escape_svn_path_none(self):      
-        self.assertEqual("", escape_svn_path(""))
-
-    def test_escape_svn_path_simple(self):
-        self.assertEqual("ab", escape_svn_path("ab"))
-
-    def test_escape_svn_path_percent(self):
-        self.assertEqual("a%25b", escape_svn_path("a%b"))
-
-    def test_escape_svn_path_whitespace(self):
-        self.assertEqual("foobar%20", escape_svn_path("foobar "))
-
-    def test_escape_svn_path_slash(self):
-        self.assertEqual("foobar%2F", escape_svn_path("foobar/"))
-
-    def test_escape_svn_path_special_char(self):
-        self.assertEqual("foobar%8A", escape_svn_path("foobar\x8a"))
-
-    def test_unescape_svn_path_slash(self):
-        self.assertEqual("foobar/", unescape_svn_path("foobar%2F"))
-
-    def test_unescape_svn_path_none(self):
-        self.assertEqual("foobar", unescape_svn_path("foobar"))
-
-    def test_unescape_svn_path_percent(self):
-        self.assertEqual("foobar%b", unescape_svn_path("foobar%25b"))
-
-    def test_escape_svn_path_nordic(self):
-        self.assertEqual("foobar%C3%A6", escape_svn_path(u"foobar\xe6".encode("utf-8")))
-
-
-class SvnRepositoryFormatTests(TestCase):
+class TestClient(TestCaseInTempDir):
     def setUp(self):
-        self.format = SvnRepositoryFormat()
-
-    def test_initialize(self):
-        self.assertRaises(UninitializableFormat, self.format.initialize, None)
-
-    def test_get_format_description(self):
-        self.assertEqual("Subversion Repository", 
-                         self.format.get_format_description())
-
-    def test_conversion_target_self(self):
-        self.assertTrue(self.format.check_conversion_target(self.format))
-
-    def test_conversion_target_incompatible(self):
-        self.assertFalse(self.format.check_conversion_target(
-              format_registry.make_bzrdir('weave').repository_format))
-
-    def test_conversion_target_compatible(self):
-        self.assertTrue(self.format.check_conversion_target(
-          format_registry.make_bzrdir('rich-root').repository_format))
+        super(TestClient, self).setUp()
 
+    def test_create(self):
+        repos.create(os.path.join(self.test_dir, "foo"))
 
+    def test_open(self):
+        repos.create(os.path.join(self.test_dir, "foo"))
+        repos.Repository("foo")
 
+    def test_uuid(self):
+        repos.create(os.path.join(self.test_dir, "foo"))
+        self.assertIsInstance(repos.Repository("foo").fs().get_uuid(), str)
diff --git a/tests/test_repository.py b/tests/test_repository.py
new file mode 100644 (file)
index 0000000..28d9bec
--- /dev/null
@@ -0,0 +1,1232 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2006-2007 Jelmer Vernooij <jelmer@samba.org>
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""Subversion repository tests."""
+
+from bzrlib import urlutils
+from bzrlib.branch import Branch
+from bzrlib.bzrdir import BzrDir, format_registry
+from bzrlib.config import GlobalConfig
+from bzrlib.errors import NoSuchRevision, UninitializableFormat, BzrError
+from bzrlib.inventory import Inventory
+from bzrlib.osutils import has_symlinks
+from bzrlib.repository import Repository
+from bzrlib.revision import NULL_REVISION, Revision
+from bzrlib.tests import TestCase
+
+import os, sys
+
+from bzrlib.plugins.svn import format
+from bzrlib.plugins.svn.mapping import (escape_svn_path, unescape_svn_path, 
+                     SVN_PROP_BZR_REVISION_ID)
+from bzrlib.plugins.svn.mapping3 import (SVN_PROP_BZR_BRANCHING_SCHEME, set_branching_scheme,
+                      set_property_scheme, BzrSvnMappingv3)
+from bzrlib.plugins.svn.mapping3.scheme import (TrunkBranchingScheme, NoBranchingScheme, 
+                    ListBranchingScheme, SingleBranchingScheme)
+from bzrlib.plugins.svn.transport import SvnRaTransport
+from bzrlib.plugins.svn.tests import TestCaseWithSubversionRepository
+from bzrlib.plugins.svn.repository import SvnRepositoryFormat
+
+
+class TestSubversionRepositoryWorks(TestCaseWithSubversionRepository):
+    def test_format(self):
+        """ Test repository format is correct """
+        bzrdir = self.make_local_bzrdir('a', 'ac')
+        self.assertEqual(bzrdir._format.get_format_string(), \
+                "Subversion Local Checkout")
+        
+        self.assertEqual(bzrdir._format.get_format_description(), \
+                "Subversion Local Checkout")
+
+    def test_get_branch_log(self):
+        repos_url = self.make_repository("a")
+        cb = self.commit_editor(repos_url)
+        cb.add_file("foo")
+        cb.done()
+
+        repos = Repository.open(repos_url)
+
+        self.assertEqual([
+            ('', {'foo': ('A', None, -1)}, 1), 
+            ('', {'': ('A', None, -1)}, 0)],
+            [(l.branch_path, l.paths, l.revnum) for l in repos.iter_reverse_branch_changes("", 1, NoBranchingScheme())])
+
+    def test_make_working_trees(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        self.assertFalse(repos.make_working_trees())
+
+    def test_get_config_global_set(self):
+        repos_url = self.make_repository("a")
+        cfg = GlobalConfig()
+        cfg.set_user_option("foo", "Still Life")
+
+        repos = Repository.open(repos_url)
+        self.assertEquals("Still Life", 
+                repos.get_config().get_user_option("foo"))
+
+    def test_get_config(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        repos.get_config().set_user_option("foo", "Van Der Graaf Generator")
+
+        repos = Repository.open(repos_url)
+        self.assertEquals("Van Der Graaf Generator", 
+                repos.get_config().get_user_option("foo"))
+
+
+    def test_get_physical_lock_status(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        self.assertFalse(repos.get_physical_lock_status())
+
+    def test_iter_changes_parent_rename(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/foo/bar': None})
+        self.client_add('dc/foo')
+        self.client_commit('dc', 'a')
+        self.client_update('dc')
+        self.client_copy('dc/foo', 'dc/bla')
+        self.client_commit('dc', 'b')
+        repos = Repository.open(repos_url)
+        ret = list(repos.iter_changes('bla/bar', 2, BzrSvnMappingv3(SingleBranchingScheme('bla/bar'))))
+        self.assertEquals(1, len(ret))
+        self.assertEquals("bla/bar", ret[0][0])
+
+    def test_set_make_working_trees(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        repos.set_make_working_trees(True)
+        self.assertFalse(repos.make_working_trees())
+
+    def test_get_fileid_map(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        mapping = repos.get_mapping()
+        self.assertEqual({u"": (mapping.generate_file_id(repos.uuid, 0, "", u""), mapping.generate_revision_id(repos.uuid, 0, ""))}, repos.get_fileid_map(0, "", mapping))
+
+    def test_generate_revision_id_forced_revid(self):
+        repos_url = self.make_client("a", "dc")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
+                             "2 someid\n")
+        self.client_commit("dc", "set id")
+        repos = Repository.open(repos_url)
+        revid = repos.generate_revision_id(1, "", repos.get_mapping())
+        self.assertEquals("someid", revid)
+
+    def test_generate_revision_id_forced_revid_invalid(self):
+        repos_url = self.make_client("a", "dc")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
+                             "corrupt-id\n")
+        self.client_commit("dc", "set id")
+        repos = Repository.open(repos_url)
+        revid = repos.generate_revision_id(1, "", repos.get_mapping())
+        self.assertEquals(
+                repos.get_mapping().generate_revision_id(repos.uuid, 1, ""),
+                revid)
+
+    def test_add_revision(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        self.assertRaises(NotImplementedError, repos.add_revision, "revid", 
+                None)
+
+    def test_has_signature_for_revision_id_no(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        self.assertFalse(repos.has_signature_for_revision_id("foo"))
+
+    def test_set_signature(self):
+        repos_url = self.make_client("a", "dc")
+        repos = Repository.open(repos_url)
+        self.build_tree({"dc/foo": "bar"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "msg")
+        revid = repos.get_mapping().generate_revision_id(repos.uuid, 1, "")
+        repos.add_signature_text(revid, "TEXT")
+        self.assertTrue(repos.has_signature_for_revision_id(revid))
+        self.assertEquals(repos.get_signature_text(revid), "TEXT")
+
+    def test_repr(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+
+        repos = Repository.open(repos_url)
+
+        self.assertEqual("SvnRepository('%s/')" % urlutils.local_path_to_url(urlutils.join(self.test_dir, "a")), repos.__repr__())
+
+    def test_get_branch_invalid_revision(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        self.assertRaises(NoSuchRevision, list, 
+               repos.iter_reverse_branch_changes("/", 20, NoBranchingScheme()))
+
+    def test_follow_branch_switched_parents(self):
+        repos_url = self.make_client('a', 'dc')
+        self.build_tree({'dc/pykleur/trunk/pykleur': None})
+        self.client_add("dc/pykleur")
+        self.client_commit("dc", "initial")
+        self.build_tree({'dc/pykleur/trunk/pykleur/afile': 'contents'})
+        self.client_add("dc/pykleur/trunk/pykleur/afile")
+        self.client_commit("dc", "add file")
+        self.client_copy("dc/pykleur", "dc/pygments", 1)
+        self.client_delete('dc/pykleur')
+        self.client_update("dc")
+        self.client_commit("dc", "commit")
+        repos = Repository.open(repos_url)
+        self.assertEquals([
+            ('pygments/trunk', {'pygments': (u'A', 'pykleur', 1),
+                                'pygments/trunk': (u'R', 'pykleur/trunk', 2),
+                                'pykleur': (u'D', None, -1)}, 3),
+            ('pykleur/trunk', {'pykleur/trunk/pykleur/afile': (u'A', None, -1)}, 2),
+            ('pykleur/trunk',
+                    {'pykleur': (u'A', None, -1),
+                     'pykleur/trunk': (u'A', None, -1),
+                     'pykleur/trunk/pykleur': (u'A', None, -1)},
+             1)],
+            [(l.branch_path, l.paths, l.revnum) for l in repos.iter_reverse_branch_changes("pygments/trunk", 3, TrunkBranchingScheme(1))])
+
+    def test_follow_branch_move_single(self):
+        repos_url = self.make_client('a', 'dc')
+        self.build_tree({'dc/pykleur/bla': None})
+        self.client_add("dc/pykleur")
+        self.client_commit("dc", "initial")
+        self.client_copy("dc/pykleur", "dc/pygments", 1)
+        self.client_update("dc")
+        self.client_commit("dc", "commit")
+        repos = Repository.open(repos_url)
+        changes = repos.iter_reverse_branch_changes("pygments", 2, SingleBranchingScheme("pygments"))
+        self.assertEquals([('pygments',
+              {'pygments/bla': ('A', None, -1), 'pygments': ('A', None, -1)},
+                2)],
+                [(l.branch_path, l.paths, l.revnum) for l in changes])
+
+    def test_history_all(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/trunk/file': "data", "dc/foo/file":"data"})
+        self.client_add("dc/trunk")
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+
+        repos = Repository.open(repos_url)
+
+        self.assertEqual(2, 
+                   len(list(repos.all_revision_ids(repos.get_layout()))))
+
+    def test_all_revs_empty(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+        self.assertEqual([], list(repos.all_revision_ids()))
+
+    def test_all_revs(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/trunk/file': "data", "dc/foo/file":"data"})
+        self.client_add("dc/trunk")
+        self.client_commit("dc", "add trunk")
+        self.build_tree({'dc/branches/somebranch/somefile': 'data'})
+        self.client_add("dc/branches")
+        self.client_commit("dc", "add a branch")
+        self.client_delete("dc/branches/somebranch")
+        self.client_commit("dc", "remove branch")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+        mapping = repos.get_mapping()
+        self.assertEqual([
+            repos.generate_revision_id(1, "trunk", mapping), 
+            repos.generate_revision_id(2, "branches/somebranch", mapping)],
+            list(repos.all_revision_ids()))
+
+    def test_follow_history_empty(self):
+        repos_url = self.make_repository("a")
+        repos = Repository.open(repos_url)
+        self.assertEqual([repos.generate_revision_id(0, '', repos.get_mapping())], 
+              list(repos.all_revision_ids(repos.get_layout())))
+
+    def test_follow_history_empty_branch(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/trunk/afile': "data", "dc/branches": None})
+        self.client_add("dc/trunk")
+        self.client_add("dc/branches")
+        self.client_commit("dc", "My Message")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+        self.assertEqual([repos.generate_revision_id(1, 'trunk', repos.get_mapping())], 
+                list(repos.all_revision_ids(repos.get_layout())))
+
+    def test_follow_history_follow(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/trunk/afile': "data", "dc/branches": None})
+        self.client_add("dc/trunk")
+        self.client_add("dc/branches")
+        self.client_commit("dc", "My Message")
+
+        self.client_copy("dc/trunk", "dc/branches/abranch")
+        self.client_commit("dc", "Create branch")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        items = list(repos.all_revision_ids(repos.get_layout()))
+        self.assertEqual([repos.generate_revision_id(1, 'trunk', repos.get_mapping()),
+                          repos.generate_revision_id(2, 'branches/abranch', repos.get_mapping())
+                          ], items)
+
+    def test_branch_log_specific(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({
+            'dc/branches': None,
+            'dc/branches/brancha': None,
+            'dc/branches/branchab': None,
+            'dc/branches/brancha/data': "data", 
+            "dc/branches/branchab/data":"data"})
+        self.client_add("dc/branches")
+        self.client_commit("dc", "My Message")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.assertEqual(1, len(list(repos.iter_reverse_branch_changes("branches/brancha",
+            1, TrunkBranchingScheme()))))
+
+    def test_branch_log_specific_ignore(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/branches': None})
+        self.client_add("dc/branches")
+        self.build_tree({
+            'dc/branches/brancha': None,
+            'dc/branches/branchab': None,
+            'dc/branches/brancha/data': "data", 
+            "dc/branches/branchab/data":"data"})
+        self.client_add("dc/branches/brancha")
+        self.client_commit("dc", "My Message")
+
+        self.client_add("dc/branches/branchab")
+        self.client_commit("dc", "My Message2")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.assertEqual(1, len(list(repos.iter_reverse_branch_changes("branches/brancha",
+            2, TrunkBranchingScheme()))))
+
+    def test_find_branches(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({
+            'dc/branches/brancha': None,
+            'dc/branches/branchab': None,
+            'dc/branches/brancha/data': "data", 
+            "dc/branches/branchab/data":"data"})
+        self.client_add("dc/branches")
+        self.client_commit("dc", "My Message")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+        branches = repos.find_branches()
+        self.assertEquals(2, len(branches))
+        self.assertEquals(urlutils.join(repos.base, "branches/brancha"), 
+                          branches[1].base)
+        self.assertEquals(urlutils.join(repos.base, "branches/branchab"), 
+                          branches[0].base)
+
+    def test_find_branchpaths_moved(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({
+            'dc/tmp/branches/brancha': None,
+            'dc/tmp/branches/branchab': None,
+            'dc/tmp/branches/brancha/data': "data", 
+            "dc/tmp/branches/branchab/data":"data"})
+        self.client_add("dc/tmp")
+        self.client_commit("dc", "My Message")
+        self.client_copy("dc/tmp/branches", "dc/tags")
+        self.client_commit("dc", "My Message 2")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.assertEqual([("tags/branchab", 2, True), 
+                          ("tags/brancha", 2, True)], 
+                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=2)))
+
+    def test_find_branchpaths_start_revno(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({'dc/branches/brancha': None})
+        self.client_add("dc/branches")
+        self.client_commit("dc", "My Message")
+        self.build_tree({'dc/branches/branchb': None})
+        self.client_add("dc/branches/branchb")
+        self.client_commit("dc", "My Message 2")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.assertEqual([("branches/branchb", 2, True)],
+                list(repos.find_branchpaths(TrunkBranchingScheme(), from_revnum=2, 
+                    to_revnum=2)))
+
+    def test_find_branchpaths_file_moved_from_nobranch(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({
+            'dc/tmp/trunk': None,
+            'dc/bla/somefile': "contents"})
+        self.client_add("dc/tmp")
+        self.client_add("dc/bla")
+        self.client_commit("dc", "My Message")
+        self.client_copy("dc/bla", "dc/tmp/branches")
+        self.client_delete("dc/tmp/branches/somefile")
+        self.client_commit("dc", "My Message 2")
+
+        Repository.open(repos_url).find_branchpaths(TrunkBranchingScheme(2))
+
+    def test_find_branchpaths_deleted_from_nobranch(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({
+            'dc/tmp/trunk': None,
+            'dc/bla/somefile': "contents"})
+        self.client_add("dc/tmp")
+        self.client_add("dc/bla")
+        self.client_commit("dc", "My Message")
+        self.client_copy("dc/bla", "dc/tmp/branches")
+        self.client_delete("dc/tmp/branches/somefile")
+        self.client_commit("dc", "My Message 2")
+
+        Repository.open(repos_url).find_branchpaths(TrunkBranchingScheme(1))
+
+    def test_find_branchpaths_moved_nobranch(self):
+        repos_url = self.make_client("a", "dc")
+        self.build_tree({
+            'dc/tmp/nested/foobar': None,
+            'dc/tmp/nested/branches/brancha': None,
+            'dc/tmp/nested/branches/branchab': None,
+            'dc/tmp/nested/branches/brancha/data': "data", 
+            "dc/tmp/nested/branches/branchab/data":"data"})
+        self.client_add("dc/tmp")
+        self.client_commit("dc", "My Message")
+        self.client_copy("dc/tmp/nested", "dc/t2")
+        self.client_commit("dc", "My Message 2")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme(1))
+
+        self.assertEqual([("t2/branches/brancha", 2, True), 
+                          ("t2/branches/branchab", 2, True)], 
+                list(repos.find_branchpaths(TrunkBranchingScheme(1), to_revnum=2)))
+
+    def test_find_branchpaths_no(self):
+        repos_url = self.make_repository("a")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, NoBranchingScheme())
+
+        self.assertEqual([("", 0, True)], 
+                list(repos.find_branchpaths(NoBranchingScheme(), to_revnum=0)))
+
+    def test_find_branchpaths_no_later(self):
+        repos_url = self.make_repository("a")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, NoBranchingScheme())
+
+        self.assertEqual([("", 0, True)], 
+                list(repos.find_branchpaths(NoBranchingScheme(), to_revnum=0)))
+
+    def test_find_branchpaths_trunk_empty(self):
+        repos_url = self.make_repository("a")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.assertEqual([], 
+                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=0)))
+
+    def test_find_branchpaths_trunk_one(self):
+        repos_url = self.make_client("a", "dc")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.build_tree({'dc/trunk/foo': "data"})
+        self.client_add("dc/trunk")
+        self.client_commit("dc", "My Message")
+
+        self.assertEqual([("trunk", 1, True)], 
+                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=1)))
+
+    def test_find_branchpaths_removed(self):
+        repos_url = self.make_client("a", "dc")
+
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+
+        self.build_tree({'dc/trunk/foo': "data"})
+        self.client_add("dc/trunk")
+        self.client_commit("dc", "My Message")
+
+        self.client_delete("dc/trunk")
+        self.client_commit("dc", "remove")
+
+        self.assertEqual([("trunk", 1, True)], 
+                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=1)))
+        self.assertEqual([("trunk", 1, False)], 
+                list(repos.find_branchpaths(TrunkBranchingScheme(), to_revnum=2)))
+
+    def test_url(self):
+        """ Test repository URL is kept """
+        bzrdir = self.make_local_bzrdir('b', 'bc')
+        self.assertTrue(isinstance(bzrdir, BzrDir))
+
+    def test_uuid(self):
+        """ Test UUID is retrieved correctly """
+        bzrdir = self.make_local_bzrdir('c', 'cc')
+        self.assertTrue(isinstance(bzrdir, BzrDir))
+        repository = bzrdir._find_repository()
+        fs = self.open_fs('c')
+        self.assertEqual(fs.get_uuid(), repository.uuid)
+
+    def test_get_inventory_weave(self):
+        bzrdir = self.make_client_and_bzrdir('d', 'dc')
+        repository = bzrdir.find_repository()
+        self.assertRaises(NotImplementedError, repository.get_inventory_weave)
+
+    def test_has_revision(self):
+        bzrdir = self.make_client_and_bzrdir('d', 'dc')
+        repository = bzrdir.find_repository()
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.assertTrue(repository.has_revision(
+            repository.generate_revision_id(1, "", repository.get_mapping())))
+        self.assertFalse(repository.has_revision("some-other-revision"))
+
+    def test_has_revision_none(self):
+        bzrdir = self.make_client_and_bzrdir('d', 'dc')
+        repository = bzrdir.find_repository()
+        self.assertTrue(repository.has_revision(None))
+
+    def test_has_revision_future(self):
+        bzrdir = self.make_client_and_bzrdir('d', 'dc')
+        repository = bzrdir.find_repository()
+        self.assertFalse(repository.has_revision(
+            repository.get_mapping().generate_revision_id(repository.uuid, 5, "")))
+
+    def test_get_parent_map(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.build_tree({'dc/foo': "data2"})
+        self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        revid = repository.generate_revision_id(0, "", mapping)
+        self.assertEqual({revid: (NULL_REVISION,)}, repository.get_parent_map([revid]))
+        revid = repository.generate_revision_id(1, "", mapping)
+        self.assertEqual({revid: (repository.generate_revision_id(0, "", mapping),)}, repository.get_parent_map([revid]))
+        revid = repository.generate_revision_id(2, "", mapping)
+        self.assertEqual({revid: (repository.generate_revision_id(1, "", mapping),)},
+            repository.get_parent_map([revid]))
+        self.assertEqual({}, repository.get_parent_map(["notexisting"]))
+
+    def test_revision_fileidmap(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_set_prop("dc", "bzr:revision-info", "")
+        self.client_set_prop("dc", "bzr:file-ids", "foo\tsomeid\n")
+        self.client_commit("dc", "My Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        tree = repository.revision_tree(Branch.open(repos_url).last_revision())
+        self.assertEqual("someid", tree.inventory.path2id("foo"))
+        self.assertFalse("1@%s::foo" % repository.uuid in tree.inventory)
+
+    def test_revision_ghost_parents(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.client_update("dc")
+        self.build_tree({'dc/foo': "data2"})
+        self.client_set_prop("dc", "bzr:ancestry:v3-none", "ghostparent\n")
+        self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual((),
+                repository.get_revision(
+                    repository.generate_revision_id(0, "", mapping)).parent_ids)
+        self.assertEqual((repository.generate_revision_id(0, "", mapping),),
+                repository.get_revision(
+                    repository.generate_revision_id(1, "", mapping)).parent_ids)
+        self.assertEqual((repository.generate_revision_id(1, "", mapping),
+            "ghostparent"), 
+                repository.get_revision(
+                    repository.generate_revision_id(2, "", mapping)).parent_ids)
+    def test_revision_svk_parent(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/trunk/foo': "data", 'dc/branches/foo': None})
+        self.client_add("dc/trunk")
+        self.client_add("dc/branches")
+        self.client_commit("dc", "My Message")
+        self.client_update("dc")
+        self.build_tree({'dc/trunk/foo': "data2"})
+        repository = Repository.open("svn+%s" % repos_url)
+        set_branching_scheme(repository, TrunkBranchingScheme())
+        self.client_set_prop("dc/trunk", "svk:merge", 
+            "%s:/branches/foo:1\n" % repository.uuid)
+        self.client_commit("dc", "Second Message")
+        mapping = repository.get_mapping()
+        self.assertEqual((repository.generate_revision_id(1, "trunk", mapping),
+            repository.generate_revision_id(1, "branches/foo", mapping)), 
+                repository.get_revision(
+                    repository.generate_revision_id(2, "trunk", mapping)).parent_ids)
+    
+    def test_get_revision(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertRaises(NoSuchRevision, repository.get_revision, 
+                "nonexisting")
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.client_update("dc")
+        self.build_tree({'dc/foo': "data2"})
+        (num, date, author) = self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        rev = repository.get_revision(
+            repository.generate_revision_id(2, "", mapping))
+        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
+                rev.parent_ids)
+        self.assertEqual(rev.revision_id, 
+                repository.generate_revision_id(2, "", mapping))
+        self.assertEqual(author, rev.committer)
+        self.assertIsInstance(rev.properties, dict)
+
+    def test_get_revision_id_overriden(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertRaises(NoSuchRevision, repository.get_revision, "nonexisting")
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.build_tree({'dc/foo': "data2"})
+        self.client_set_prop("dc", "bzr:revision-id:v3-none", 
+                            "3 myrevid\n")
+        self.client_update("dc")
+        (num, date, author) = self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        revid = mapping.generate_revision_id(repository.uuid, 2, "")
+        rev = repository.get_revision("myrevid")
+        self.assertEqual((repository.generate_revision_id(1, "", mapping),),
+                rev.parent_ids)
+        self.assertEqual(rev.revision_id, 
+                         repository.generate_revision_id(2, "", mapping))
+        self.assertEqual(author, rev.committer)
+        self.assertIsInstance(rev.properties, dict)
+
+    def test_get_revision_zero(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        rev = repository.get_revision(
+            repository.generate_revision_id(0, "", mapping))
+        self.assertEqual(repository.generate_revision_id(0, "", mapping), 
+                         rev.revision_id)
+        self.assertEqual("", rev.committer)
+        self.assertEqual({}, rev.properties)
+        self.assertEqual(None, rev.timezone)
+
+    def test_store_branching_scheme(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open(repos_url)
+        set_branching_scheme(repository, TrunkBranchingScheme(42))
+        repository = Repository.open(repos_url)
+        self.assertEquals("trunk42", str(repository.get_mapping().scheme))
+
+    def test_get_ancestry(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertRaises(NoSuchRevision, repository.get_revision, "nonexisting")
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.client_update("dc")
+        self.build_tree({'dc/foo': "data2"})
+        self.client_commit("dc", "Second Message")
+        self.client_update("dc")
+        self.build_tree({'dc/foo': "data3"})
+        self.client_commit("dc", "Third Message")
+        self.client_update("dc")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual([None, 
+            repository.generate_revision_id(0, "", mapping),
+            repository.generate_revision_id(1, "", mapping),
+            repository.generate_revision_id(2, "", mapping),
+            repository.generate_revision_id(3, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(3, "", mapping)))
+        self.assertEqual([None, 
+            repository.generate_revision_id(0, "", mapping),
+            repository.generate_revision_id(1, "", mapping),
+            repository.generate_revision_id(2, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(2, "", mapping)))
+        self.assertEqual([None,
+                    repository.generate_revision_id(0, "", mapping),
+                    repository.generate_revision_id(1, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(1, "", mapping)))
+        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(0, "", mapping)))
+        self.assertEqual([None], repository.get_ancestry(NULL_REVISION))
+
+    def test_get_ancestry2(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.build_tree({'dc/foo': "data2"})
+        self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(0, "", mapping)))
+        self.assertEqual([None, repository.generate_revision_id(0, "", mapping),
+            repository.generate_revision_id(1, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(1, "", mapping)))
+        self.assertEqual([None, 
+            repository.generate_revision_id(0, "", mapping),
+            repository.generate_revision_id(1, "", mapping),
+            repository.generate_revision_id(2, "", mapping)], 
+                repository.get_ancestry(
+                    repository.generate_revision_id(2, "", mapping)))
+
+    def test_get_ancestry_merged(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.client_update("dc")
+        self.client_set_prop("dc", "bzr:ancestry:v3-none", "a-parent\n")
+        self.build_tree({'dc/foo': "data2"})
+        self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual([None, repository.generate_revision_id(0, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(0, "", mapping)))
+        self.assertEqual([None, repository.generate_revision_id(0, "", mapping),
+            repository.generate_revision_id(1, "", mapping)],
+                repository.get_ancestry(
+                    repository.generate_revision_id(1, "", mapping)))
+        self.assertEqual([None, 
+            repository.generate_revision_id(0, "", mapping), "a-parent", 
+            repository.generate_revision_id(1, "", mapping), 
+                  repository.generate_revision_id(2, "", mapping)], 
+                repository.get_ancestry(
+                    repository.generate_revision_id(2, "", mapping)))
+
+    def test_get_inventory(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertRaises(NoSuchRevision, repository.get_inventory, 
+                "nonexisting")
+        self.build_tree({'dc/foo': "data", 'dc/blah': "other data"})
+        self.client_add("dc/foo")
+        self.client_add("dc/blah")
+        self.client_commit("dc", "My Message") #1
+        self.client_update("dc")
+        self.build_tree({'dc/foo': "data2", "dc/bar/foo": "data3"})
+        self.client_add("dc/bar")
+        self.client_commit("dc", "Second Message") #2
+        self.client_update("dc")
+        self.build_tree({'dc/foo': "data3"})
+        self.client_commit("dc", "Third Message") #3
+        self.client_update("dc")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        inv = repository.get_inventory(
+                repository.generate_revision_id(1, "", mapping))
+        self.assertIsInstance(inv, Inventory)
+        self.assertIsInstance(inv.path2id("foo"), basestring)
+        inv = repository.get_inventory(
+            repository.generate_revision_id(2, "", mapping))
+        self.assertEqual(repository.generate_revision_id(2, "", mapping), 
+                         inv[inv.path2id("foo")].revision)
+        self.assertEqual(repository.generate_revision_id(1, "", mapping), 
+                         inv[inv.path2id("blah")].revision)
+        self.assertIsInstance(inv, Inventory)
+        self.assertIsInstance(inv.path2id("foo"), basestring)
+        self.assertIsInstance(inv.path2id("bar"), basestring)
+        self.assertIsInstance(inv.path2id("bar/foo"), basestring)
+
+    def test_generate_revision_id(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/bla/bloe': None})
+        self.client_add("dc/bla")
+        self.client_commit("dc", "bla")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual(
+               mapping.generate_revision_id(repository.uuid, 1, "bla/bloe"), 
+            repository.generate_revision_id(1, "bla/bloe", mapping))
+
+    def test_generate_revision_id_zero(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual(mapping.generate_revision_id(repository.uuid, 0, ""), 
+                repository.generate_revision_id(0, "", mapping))
+
+    def test_lookup_revision_id(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/bloe': None})
+        self.client_add("dc/bloe")
+        self.client_commit("dc", "foobar")
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertRaises(NoSuchRevision, repository.lookup_revision_id, 
+            "nonexisting")
+        mapping = repository.get_mapping()
+        self.assertEqual(("bloe", 1), 
+            repository.lookup_revision_id(
+                repository.generate_revision_id(1, "bloe", mapping))[:2])
+
+    def test_lookup_revision_id_overridden(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/bloe': None})
+        self.client_add("dc/bloe")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", "2 myid\n")
+        self.client_commit("dc", "foobar")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual(("", 1), repository.lookup_revision_id( 
+            mapping.generate_revision_id(repository.uuid, 1, ""))[:2])
+        self.assertEqual(("", 1), 
+                repository.lookup_revision_id("myid")[:2])
+
+    def test_lookup_revision_id_overridden_invalid(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/bloe': None})
+        self.client_add("dc/bloe")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
+                             "corrupt-entry\n")
+        self.client_commit("dc", "foobar")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual(("", 1), repository.lookup_revision_id( 
+            mapping.generate_revision_id(repository.uuid, 1, ""))[:2])
+        self.assertRaises(NoSuchRevision, repository.lookup_revision_id, 
+            "corrupt-entry")
+
+    def test_lookup_revision_id_overridden_invalid_dup(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/bloe': None})
+        self.client_add("dc/bloe")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
+                             "corrupt-entry\n")
+        self.client_commit("dc", "foobar")
+        self.build_tree({'dc/bla': None})
+        self.client_add("dc/bla")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", 
+                "corrupt-entry\n2 corrupt-entry\n")
+        self.client_commit("dc", "foobar")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertEqual(("", 2), repository.lookup_revision_id( 
+            mapping.generate_revision_id(repository.uuid, 2, ""))[:2])
+        self.assertEqual(("", 1), repository.lookup_revision_id( 
+            mapping.generate_revision_id(repository.uuid, 1, ""))[:2])
+        self.assertEqual(("", 2), repository.lookup_revision_id( 
+            "corrupt-entry")[:2])
+
+    def test_lookup_revision_id_overridden_not_found(self):
+        """Make sure a revision id that is looked up but doesn't exist 
+        doesn't accidently end up in the revid cache."""
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/bloe': None})
+        self.client_add("dc/bloe")
+        self.client_set_prop("dc", SVN_PROP_BZR_REVISION_ID+"none", "2 myid\n")
+        self.client_commit("dc", "foobar")
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertRaises(NoSuchRevision, 
+                repository.lookup_revision_id, "foobar")
+
+    def test_set_branching_scheme_property(self):
+        repos_url = self.make_client('d', 'dc')
+        self.client_set_prop("dc", SVN_PROP_BZR_BRANCHING_SCHEME, 
+            "trunk\nbranches/*\nbranches/tmp/*")
+        self.client_commit("dc", "set scheme")
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertEquals(ListBranchingScheme(["trunk", "branches/*", "branches/tmp/*"]).branch_list,
+                          repository.get_mapping().scheme.branch_list)
+
+    def test_set_property_scheme(self):
+        repos_url = self.make_client('d', 'dc')
+        repos = Repository.open(repos_url)
+        set_property_scheme(repos, ListBranchingScheme(["bla/*"]))
+        self.client_update("dc")
+        self.assertEquals("bla/*\n", 
+                   self.client_get_prop("dc", SVN_PROP_BZR_BRANCHING_SCHEME))
+        self.assertEquals("Updating branching scheme for Bazaar.", 
+                self.client_log("dc", 1, 1)[1][3])
+
+    def test_lookup_revision_id_invalid_uuid(self):
+        repos_url = self.make_client('d', 'dc')
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        self.assertRaises(NoSuchRevision, 
+            repository.lookup_revision_id, 
+                mapping.generate_revision_id("invaliduuid", 0, ""))
+        
+    def test_check(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+        repository.check([
+            repository.generate_revision_id(0, "", mapping), 
+            repository.generate_revision_id(1, "", mapping)])
+
+    def test_copy_contents_into(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo/bla': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.build_tree({'dc/foo/blo': "data2", "dc/bar/foo": "data3", 'dc/foo/bla': "data"})
+        self.client_add("dc/foo/blo")
+        self.client_add("dc/bar")
+        self.client_commit("dc", "Second Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        mapping = repository.get_mapping()
+
+        to_bzrdir = BzrDir.create("e", format.get_rich_root_format())
+        to_repos = to_bzrdir.create_repository()
+
+        repository.copy_content_into(to_repos, 
+                repository.generate_revision_id(2, "", mapping))
+
+        self.assertTrue(repository.has_revision(
+            repository.generate_revision_id(2, "", mapping)))
+        self.assertTrue(repository.has_revision(
+            repository.generate_revision_id(1, "", mapping)))
+
+    def test_is_shared(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo/bla': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        repository = Repository.open("svn+%s" % repos_url)
+        self.assertTrue(repository.is_shared())
+
+    def test_fetch_property_change_only_trunk(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/trunk/bla': "data"})
+        self.client_add("dc/trunk")
+        self.client_commit("dc", "My Message")
+        self.client_set_prop("dc/trunk", "some:property", "some data\n")
+        self.client_commit("dc", "My 3")
+        self.client_set_prop("dc/trunk", "some2:property", "some data\n")
+        self.client_commit("dc", "My 2")
+        self.client_set_prop("dc/trunk", "some:property", "some other data\n")
+        self.client_commit("dc", "My 4")
+        oldrepos = Repository.open("svn+"+repos_url)
+        self.assertEquals([('trunk', {'trunk': (u'M', None, -1)}, 3), 
+                           ('trunk', {'trunk': (u'M', None, -1)}, 2), 
+                           ('trunk', {'trunk/bla': (u'A', None, -1), 'trunk': (u'A', None, -1)}, 1)], 
+                   [(l.branch_path, l.paths, l.revnum) for l in oldrepos.iter_reverse_branch_changes("trunk", 3, TrunkBranchingScheme())])
+
+    def test_control_code_msg(self):
+        repos_url = self.make_client('d', 'dc')
+
+        self.build_tree({'dc/trunk': None})
+        self.client_add("dc/trunk")
+        self.client_commit("dc", "\x24")
+
+        self.build_tree({'dc/trunk/hosts': 'hej2'})
+        self.client_add("dc/trunk/hosts")
+        self.client_commit("dc", "bla\xfcbla") #2
+
+        self.build_tree({'dc/trunk/hosts': 'hej3'})
+        self.client_commit("dc", "a\x0cb") #3
+
+        self.build_tree({'dc/branches/foobranch/file': 'foohosts'})
+        self.client_add("dc/branches")
+        self.client_commit("dc", "foohosts") #4
+
+        oldrepos = Repository.open("svn+"+repos_url)
+        set_branching_scheme(oldrepos, TrunkBranchingScheme())
+        dir = BzrDir.create("f",format=format.get_rich_root_format())
+        newrepos = dir.create_repository()
+        oldrepos.copy_content_into(newrepos)
+
+        mapping = oldrepos.get_mapping()
+
+        self.assertTrue(newrepos.has_revision(
+            oldrepos.generate_revision_id(1, "trunk", mapping)))
+        self.assertTrue(newrepos.has_revision(
+            oldrepos.generate_revision_id(2, "trunk", mapping)))
+        self.assertTrue(newrepos.has_revision(
+            oldrepos.generate_revision_id(3, "trunk", mapping)))
+        self.assertTrue(newrepos.has_revision(
+            oldrepos.generate_revision_id(4, "branches/foobranch", mapping)))
+        self.assertFalse(newrepos.has_revision(
+            oldrepos.generate_revision_id(4, "trunk", mapping)))
+        self.assertFalse(newrepos.has_revision(
+            oldrepos.generate_revision_id(2, "", mapping)))
+
+        rev = newrepos.get_revision(oldrepos.generate_revision_id(1, "trunk", mapping))
+        self.assertEqual("$", rev.message)
+
+        rev = newrepos.get_revision(
+            oldrepos.generate_revision_id(2, "trunk", mapping))
+        self.assertEqual('bla\xc3\xbcbla', rev.message.encode("utf-8"))
+
+        rev = newrepos.get_revision(oldrepos.generate_revision_id(3, "trunk", mapping))
+        self.assertEqual(u"a\\x0cb", rev.message)
+
+    def test_set_branching_scheme(self):
+        repos_url = self.make_client('d', 'dc')
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, NoBranchingScheme())
+
+    def testlhs_revision_parent_none(self):
+        repos_url = self.make_client('d', 'dc')
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, NoBranchingScheme())
+        self.assertEquals(NULL_REVISION, repos.lhs_revision_parent("", 0, NoBranchingScheme()))
+
+    def testlhs_revision_parent_first(self):
+        repos_url = self.make_client('d', 'dc')
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, NoBranchingScheme())
+        self.build_tree({'dc/adir/afile': "data"})
+        self.client_add("dc/adir")
+        self.client_commit("dc", "Initial commit")
+        mapping = repos.get_mapping()
+        self.assertEquals(repos.generate_revision_id(0, "", mapping), \
+                repos.lhs_revision_parent("", 1, mapping))
+
+    def testlhs_revision_parent_simple(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/trunk/adir/afile': "data", 
+                         'dc/trunk/adir/stationary': None,
+                         'dc/branches/abranch': None})
+        self.client_add("dc/trunk")
+        self.client_add("dc/branches")
+        self.client_commit("dc", "Initial commit")
+        self.build_tree({'dc/trunk/adir/afile': "bla"})
+        self.client_commit("dc", "Incremental commit")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme())
+        mapping = repos.get_mapping()
+        self.assertEquals(repos.generate_revision_id(1, "trunk", mapping), \
+                repos.lhs_revision_parent("trunk", 2, mapping))
+
+    def testlhs_revision_parent_copied(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/py/trunk/adir/afile': "data", 
+                         'dc/py/trunk/adir/stationary': None})
+        self.client_add("dc/py")
+        self.client_commit("dc", "Initial commit")
+        self.client_copy("dc/py", "dc/de")
+        self.client_commit("dc", "Incremental commit")
+        self.build_tree({'dc/de/trunk/adir/afile': "bla"})
+        self.client_commit("dc", "Change de")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme(1))
+        mapping = repos.get_mapping()
+        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
+                repos.lhs_revision_parent("de/trunk", 3, mapping))
+
+    def test_mainline_revision_copied(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/py/trunk/adir/afile': "data", 
+                         'dc/py/trunk/adir/stationary': None})
+        self.client_add("dc/py")
+        self.client_commit("dc", "Initial commit")
+        self.build_tree({'dc/de':None})
+        self.client_add("dc/de")
+        self.client_copy("dc/py/trunk", "dc/de/trunk")
+        self.client_commit("dc", "Copy trunk")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme(1))
+        mapping = repos.get_mapping()
+        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
+                repos.lhs_revision_parent("de/trunk", 2, mapping))
+
+    def test_mainline_revision_nested_deleted(self):
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/py/trunk/adir/afile': "data", 
+                         'dc/py/trunk/adir/stationary': None})
+        self.client_add("dc/py")
+        self.client_commit("dc", "Initial commit")
+        self.client_copy("dc/py", "dc/de")
+        self.client_commit("dc", "Incremental commit")
+        self.client_delete("dc/de/trunk/adir")
+        self.client_commit("dc", "Another incremental commit")
+        repos = Repository.open(repos_url)
+        set_branching_scheme(repos, TrunkBranchingScheme(1))
+        mapping = repos.get_mapping()
+        self.assertEquals(repos.generate_revision_id(1, "py/trunk", mapping), \
+                repos.lhs_revision_parent("de/trunk", 3, mapping))
+
+    def test_mainline_revision_missing(self):
+        repos_url = self.make_client('d', 'dc')
+        repos = Repository.open(repos_url)
+        self.build_tree({'dc/py/trunk/adir/afile': "data", 
+                         'dc/py/trunk/adir/stationary': None})
+        self.client_add("dc/py")
+        self.client_commit("dc", "Initial commit")
+        self.assertRaises(NoSuchRevision, 
+                lambda: repos.lhs_revision_parent("trunk", 2, repos.get_mapping()))
+
+
+class TestSvnRevisionTree(TestCaseWithSubversionRepository):
+    def setUp(self):
+        super(TestSvnRevisionTree, self).setUp()
+        repos_url = self.make_client('d', 'dc')
+        self.build_tree({'dc/foo/bla': "data"})
+        self.client_add("dc/foo")
+        self.client_commit("dc", "My Message")
+        self.repos = Repository.open(repos_url)
+        mapping = self.repos.get_mapping()
+        self.inventory = self.repos.get_inventory(
+                self.repos.generate_revision_id(1, "", mapping))
+        self.tree = self.repos.revision_tree(
+                self.repos.generate_revision_id(1, "", mapping))
+
+    def test_inventory(self):
+        self.assertIsInstance(self.tree.inventory, Inventory)
+        self.assertEqual(self.inventory, self.tree.inventory)
+
+    def test_get_parent_ids(self):
+        mapping = self.repos.get_mapping()
+        self.assertEqual((self.repos.generate_revision_id(0, "", mapping),), self.tree.get_parent_ids())
+
+    def test_get_parent_ids_zero(self):
+        mapping = self.repos.get_mapping()
+        tree = self.repos.revision_tree(
+                self.repos.generate_revision_id(0, "", mapping))
+        self.assertEqual((), tree.get_parent_ids())
+
+    def test_get_revision_id(self):
+        mapping = self.repos.get_mapping()
+        self.assertEqual(self.repos.generate_revision_id(1, "", mapping),
+                         self.tree.get_revision_id())
+
+    def test_get_file_lines(self):
+        self.assertEqual(["data"], 
+                self.tree.get_file_lines(self.inventory.path2id("foo/bla")))
+
+    def test_executable(self):
+        self.client_set_prop("dc/foo/bla", "svn:executable", "*")
+        self.client_commit("dc", "My Message")
+
+        mapping = self.repos.get_mapping()
+        
+        inventory = self.repos.get_inventory(
+                self.repos.generate_revision_id(2, "", mapping))
+
+        self.assertTrue(inventory[inventory.path2id("foo/bla")].executable)
+
+    def test_symlink(self):
+        if not has_symlinks():
+            return
+        os.symlink('foo/bla', 'dc/bar')
+        self.client_add('dc/bar')
+        self.client_commit("dc", "My Message")
+
+        mapping = self.repos.get_mapping()
+        
+        inventory = self.repos.get_inventory(
+                self.repos.generate_revision_id(2, "", mapping))
+
+        self.assertEqual('symlink', inventory[inventory.path2id("bar")].kind)
+        self.assertEqual('foo/bla', 
+                inventory[inventory.path2id("bar")].symlink_target)
+
+    def test_not_executable(self):
+        self.assertFalse(self.inventory[
+            self.inventory.path2id("foo/bla")].executable)
+
+
+class EscapeTest(TestCase):
+    def test_escape_svn_path_none(self):      
+        self.assertEqual("", escape_svn_path(""))
+
+    def test_escape_svn_path_simple(self):
+        self.assertEqual("ab", escape_svn_path("ab"))
+
+    def test_escape_svn_path_percent(self):
+        self.assertEqual("a%25b", escape_svn_path("a%b"))
+
+    def test_escape_svn_path_whitespace(self):
+        self.assertEqual("foobar%20", escape_svn_path("foobar "))
+
+    def test_escape_svn_path_slash(self):
+        self.assertEqual("foobar%2F", escape_svn_path("foobar/"))
+
+    def test_escape_svn_path_special_char(self):
+        self.assertEqual("foobar%8A", escape_svn_path("foobar\x8a"))
+
+    def test_unescape_svn_path_slash(self):
+        self.assertEqual("foobar/", unescape_svn_path("foobar%2F"))
+
+    def test_unescape_svn_path_none(self):
+        self.assertEqual("foobar", unescape_svn_path("foobar"))
+
+    def test_unescape_svn_path_percent(self):
+        self.assertEqual("foobar%b", unescape_svn_path("foobar%25b"))
+
+    def test_escape_svn_path_nordic(self):
+        self.assertEqual("foobar%C3%A6", escape_svn_path(u"foobar\xe6".encode("utf-8")))
+
+
+class SvnRepositoryFormatTests(TestCase):
+    def setUp(self):
+        self.format = SvnRepositoryFormat()
+
+    def test_initialize(self):
+        self.assertRaises(UninitializableFormat, self.format.initialize, None)
+
+    def test_get_format_description(self):
+        self.assertEqual("Subversion Repository", 
+                         self.format.get_format_description())
+
+    def test_conversion_target_self(self):
+        self.assertTrue(self.format.check_conversion_target(self.format))
+
+    def test_conversion_target_incompatible(self):
+        self.assertFalse(self.format.check_conversion_target(
+              format_registry.make_bzrdir('weave').repository_format))
+
+    def test_conversion_target_compatible(self):
+        self.assertTrue(self.format.check_conversion_target(
+          format_registry.make_bzrdir('rich-root').repository_format))
+
+
+
diff --git a/tests/test_wc.py b/tests/test_wc.py
new file mode 100644 (file)
index 0000000..04a0fdb
--- /dev/null
@@ -0,0 +1,39 @@
+# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Subversion ra library tests."""
+
+from bzrlib.tests import TestCase
+from bzrlib.plugins.svn import wc
+
+class VersionTest(TestCase):
+    def test_version_length(self):
+        self.assertEquals(4, len(wc.version()))
+
+class WorkingCopyTests(TestCase):
+    def test_get_adm_dir(self):
+        self.assertEquals(".svn", wc.get_adm_dir())
+
+    def test_is_normal_prop(self):
+        self.assertTrue(wc.is_normal_prop("svn:ignore"))
+
+    def test_is_entry_prop(self):
+        self.assertTrue(wc.is_entry_prop("svn:entry:foo"))
+
+    def test_is_wc_prop(self):
+        self.assertTrue(wc.is_wc_prop("svn:wc:foo"))
+
+    def test_get_default_ignores(self):
+        self.assertIsInstance(wc.get_default_ignores({}), list)
index 3bfe25dc8229cc9756380ed886ae60dad85c7fd4..c3c8d20ae48d5379386b3736aa8618694cc59917 100644 (file)
@@ -25,8 +25,7 @@ from bzrlib.tests import KnownFailure, TestCase
 from bzrlib.trace import mutter
 from bzrlib.workingtree import WorkingTree
 
-import svn.core
-import svn.wc
+from bzrlib.plugins.svn import core, wc
 
 import os, sys
 
@@ -208,13 +207,13 @@ class TestWorkingTree(TestCaseWithSubversionRepository):
     def test_get_ignore_list_empty(self):
         self.make_client('a', 'dc')
         tree = self.open_checkout("dc")
-        self.assertEqual(set([".svn"] + svn.wc.get_default_ignores(svn_config)), tree.get_ignore_list())
+        self.assertEqual(set([".svn"] + wc.get_default_ignores(svn_config)), tree.get_ignore_list())
 
     def test_get_ignore_list_onelevel(self):
         self.make_client('a', 'dc')
         self.client_set_prop("dc", "svn:ignore", "*.d\n*.c\n")
         tree = self.open_checkout("dc")
-        self.assertEqual(set([".svn"] + svn.wc.get_default_ignores(svn_config) + ["./*.d", "./*.c"]), tree.get_ignore_list())
+        self.assertEqual(set([".svn"] + wc.get_default_ignores(svn_config) + ["./*.d", "./*.c"]), tree.get_ignore_list())
 
     def test_get_ignore_list_morelevel(self):
         self.make_client('a', 'dc')
@@ -223,7 +222,7 @@ class TestWorkingTree(TestCaseWithSubversionRepository):
         self.client_add("dc/x")
         self.client_set_prop("dc/x", "svn:ignore", "*.e\n")
         tree = self.open_checkout("dc")
-        self.assertEqual(set([".svn"] + svn.wc.get_default_ignores(svn_config) + ["./*.d", "./*.c", "./x/*.e"]), tree.get_ignore_list())
+        self.assertEqual(set([".svn"] + wc.get_default_ignores(svn_config) + ["./*.d", "./*.c", "./x/*.e"]), tree.get_ignore_list())
 
     def test_add_reopen(self):
         self.make_client('a', 'dc')
@@ -336,7 +335,13 @@ class TestWorkingTree(TestCaseWithSubversionRepository):
         self.build_tree({"dc/bl": "data"})
         tree = self.open_checkout("dc")
         self.assertEqual([], tree.pending_merges())
+
+    def test_get_parent_ids(self):
+        self.make_client('a', 'dc')
+        self.build_tree({"dc/bl": "data"})
+        tree = self.open_checkout("dc")
+        self.assertEqual([Branch.open('a').last_revision()], tree.get_parent_ids()) 
+
     def test_delta(self):
         self.make_client('a', 'dc')
         self.build_tree({"dc/bl": "data"})
index 2da33e71ddc2f17f4ce3a31f5121649c1965bbca..9c1cd8e8f7771973fe2253081d46221e2bf5e740 100644 (file)
@@ -21,31 +21,20 @@ from bzrlib.errors import (NoSuchFile, NotBranchError, TransportNotPossible,
 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 bzrlib.plugins.svn.core import SubversionException
+from bzrlib.plugins.svn.auth import create_auth_baton
 
-from bzrlib.plugins.svn import properties
+from bzrlib.plugins.svn import core, properties, ra
 from bzrlib.plugins.svn.errors import convert_svn_error, NoSvnRepositoryPresent, ERR_BAD_URL, ERR_RA_SVN_REPOS_NOT_FOUND, ERR_FS_ALREADY_EXISTS, ERR_FS_NOT_FOUND, ERR_FS_NOT_DIRECTORY
 import urlparse
 import urllib
 
-svn_config = svn.core.svn_config_get_config(None)
+svn_config = core.get_config()
 
 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():
@@ -101,373 +90,23 @@ def needs_busy(unbound):
     return convert
 
 
-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
-
-
-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._client = create_svn_client(url)
-        self._unbusy_handler = None
-        try:
-            self.mutter('opening SVN RA connection to %r', url)
-            self._ra = svn.client.open_ra_session(url.encode('utf8'), 
-                    self._client)
-        except SubversionException, (_, num):
-            if num == ERR_RA_SVN_REPOS_NOT_FOUND:
-                raise NoSvnRepositoryPresent(url=url)
-            if num == 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()
-
-    def is_busy(self):
-        return self._busy
-
-    def _mark_busy(self):
-        assert not self._busy, "already busy"
-        self._busy = True
-
-    def set_unbusy_handler(self, handler):
-        self._unbusy_handler = handler
-
-    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, *args):
-        if 'transport' in debug.debug_flags:
-            mutter(text, *args)
-
-    @convert_svn_error
-    @needs_busy
-    def get_uuid(self):
-        self.mutter('svn get-uuid')
-        return svn.ra.get_uuid(self._ra)
-
-    @convert_svn_error
-    @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)
-        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
-
-    @convert_svn_error
-    def do_switch(self, switch_rev, recurse, switch_url, editor, pool=None):
-        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))
-
-    @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)
-
-    @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)
-    @convert_svn_error
-    @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] != "/"
-        # 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)
-
-    @convert_svn_error
-    @needs_busy
-    def check_path(self, path, revnum):
-        assert len(path) == 0 or path[0] != "/"
-        self.mutter("svn check_path -r%d %s", revnum, path)
-        return svn.ra.check_path(self._ra, 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.url, relpath)
-        try:
-            svn.client.mkdir([path.encode("utf-8")], self._client)
-        except SubversionException, (msg, num):
-            if num == ERR_FS_NOT_FOUND:
-                raise NoSuchFile(path)
-            if num == ERR_FS_ALREADY_EXISTS:
-                raise FileExists(path)
-            raise
+def Connection(url):
+    try:
+        mutter('opening SVN RA connection to %r' % url)
+        ret = ra.RemoteAccess(url.encode('utf8'), 
+                auth=create_auth_baton(url))
+        # FIXME: Callbacks
+    except SubversionException, (_, num):
+        if num in (ERR_RA_SVN_REPOS_NOT_FOUND,):
+            raise NoSvnRepositoryPresent(url=url)
+        if num == ERR_BAD_URL:
+            raise InvalidURL(url)
+        raise
 
-    @convert_svn_error
-    def replay(self, revision, low_water_mark, send_deltas, editor, pool=None):
-        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)
+    from bzrlib.plugins.svn import lazy_check_versions
+    lazy_check_versions()
 
-    @convert_svn_error
-    def do_update(self, revnum, recurse, editor, pool=None):
-        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))
-
-    @convert_svn_error
-    def has_capability(self, cap):
-        return svn.ra.has_capability(self._ra, cap)
-
-    @convert_svn_error
-    def revprop_list(self, revnum, pool=None):
-        self.mutter('svn revprop-list -r %r', revnum)
-        return svn.ra.rev_proplist(self._ra, revnum, pool)
-
-    @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() != [properties.PROP_REVISION_LOG]:
-                raise NotImplementedError()
-            else:
-                editor = svn.ra.get_commit_editor2(self._ra, 
-                            revprops[properties.PROP_REVISION_LOG],
-                            done_cb, lock_token, keep_locks)
-
-            return Editor(self, editor)
-        except:
-            self._unmark_busy()
-            raise
-
-    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):
-        # No paths starting with slash, please
-        assert paths is None or all([not p.startswith("/") for p in paths])
-        if (paths is None and 
-            (svn.core.SVN_VER_MINOR < 6 or (
-             svn.core.SVN_VER_REVISION < 31470 and svn.core.SVN_VER_REVISION != 0))):
-            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 properties.PROP_REVISION_AUTHOR in revprops:
-                    self.revprops[properties.PROP_REVISION_AUTHOR] = author
-                if properties.PROP_REVISION_LOG in revprops:
-                    self.revprops[properties.PROP_REVISION_LOG] = message
-                if properties.PROP_REVISION_DATE in revprops:
-                    self.revprops[properties.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)
+    return ret
 
 
 class ConnectionPool(object):
@@ -478,7 +117,7 @@ class ConnectionPool(object):
     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"
+            assert not c.busy, "busy connection in pool"
             if c.url == url:
                 self.connections.remove(c)
                 return c
@@ -497,7 +136,7 @@ class ConnectionPool(object):
             raise
 
     def add(self, connection):
-        assert not connection.is_busy(), "adding busy connection in pool"
+        assert not connection.busy, "adding busy connection in pool"
         self.connections.add(connection)
     
 
@@ -508,7 +147,6 @@ class SvnRaTransport(Transport):
     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 
@@ -553,6 +191,7 @@ class SvnRaTransport(Transport):
 
     def get_uuid(self):
         conn = self.get_connection()
+        self.mutter('svn get-uuid')
         try:
             return conn.get_uuid()
         finally:
@@ -567,6 +206,7 @@ class SvnRaTransport(Transport):
 
     def get_svn_repos_root(self):
         conn = self.get_connection()
+        self.mutter('svn get-repos-root')
         try:
             return conn.get_repos_root()
         finally:
@@ -574,19 +214,19 @@ class SvnRaTransport(Transport):
 
     def get_latest_revnum(self):
         conn = self.get_connection()
+        self.mutter('svn get-latest-revnum')
         try:
             return conn.get_latest_revnum()
         finally:
             self.add_connection(conn)
 
-    def do_switch(self, switch_rev, recurse, switch_url, editor, pool=None):
+    def do_switch(self, switch_rev, recurse, switch_url, editor):
         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)
+        self.mutter('svn do-switch -r%d %s' % (switch_rev, switch_url))
+        return conn.do_switch(switch_rev, "", recurse, switch_url, editor)
 
     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)
@@ -594,10 +234,11 @@ class SvnRaTransport(Transport):
         from threading import Thread, Semaphore
 
         class logfetcher(Thread):
-            def __init__(self, transport, **kwargs):
+            def __init__(self, transport, *args, **kwargs):
                 Thread.__init__(self)
                 self.setDaemon(True)
                 self.transport = transport
+                self.args = args
                 self.kwargs = kwargs
                 self.pending = []
                 self.conn = None
@@ -615,12 +256,12 @@ class SvnRaTransport(Transport):
 
             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))
+                def rcvr(*args):
+                    self.pending.append(args)
                     self.semaphore.release()
                 self.conn = self.transport.get_connection()
                 try:
-                    self.conn.get_log(rcvr=rcvr, **self.kwargs)
+                    self.conn.get_log(callback=rcvr, *self.args, **self.kwargs)
                     self.pending.append(None)
                 except Exception, e:
                     self.pending.append(e)
@@ -631,15 +272,17 @@ class SvnRaTransport(Transport):
         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 = logfetcher(self, paths=newpaths, start=from_revnum, end=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):
+    def get_log(self, rcvr, paths, from_revnum, to_revnum, limit, discover_changed_paths, 
+                strict_node_history, revprops):
         assert paths is None or isinstance(paths, list), "Invalid paths"
         assert paths is None or all([isinstance(x, str) for x in paths])
 
+        self.mutter('svn log -r%d:%d %r' % (from_revnum, to_revnum, paths))
+
         if paths is None:
             newpaths = None
         else:
@@ -647,10 +290,10 @@ class SvnRaTransport(Transport):
 
         conn = self.get_connection()
         try:
-            return conn.get_log(newpaths, 
+            return conn.get_log(rcvr, newpaths, 
                     from_revnum, to_revnum,
                     limit, discover_changed_paths, strict_node_history, 
-                    revprops, rcvr, pool)
+                    revprops)
         finally:
             self.add_connection(conn)
 
@@ -659,18 +302,20 @@ class SvnRaTransport(Transport):
             return self.connections.get(self.svn_url)
         return self.get_connection()
 
-    def change_rev_prop(self, revnum, name, value, pool=None):
+    def change_rev_prop(self, revnum, name, value):
         conn = self.get_connection()
+        self.mutter('svn change-revprop -r%d %s=%s' % (revnum, name, value))
         try:
-            return conn.change_rev_prop(revnum, name, value, pool)
+            return conn.change_rev_prop(revnum, name, value)
         finally:
             self.add_connection(conn)
 
-    def get_dir(self, path, revnum, pool=None, kind=False):
+    def get_dir(self, path, revnum, kind=False):
         path = self._request_path(path)
         conn = self.get_connection()
+        self.mutter('svn get-dir -r%d %s' % (revnum, path))
         try:
-            return conn.get_dir(path, revnum, pool, kind)
+            return conn.get_dir(path, revnum, kind)
         finally:
             self.add_connection(conn)
 
@@ -703,50 +348,65 @@ class SvnRaTransport(Transport):
     def check_path(self, path, revnum):
         path = self._request_path(path)
         conn = self.get_connection()
+        self.mutter('svn check-path -r%d %s' % (revnum, path))
         try:
             return conn.check_path(path, revnum)
         finally:
             self.add_connection(conn)
 
-    def mkdir(self, relpath, mode=None):
+    def mkdir(self, relpath, message="Creating directory"):
         conn = self.get_connection()
+        self.mutter('svn mkdir %s' % (relpath,))
         try:
-            return conn.mkdir(relpath, mode)
+            ce = conn.get_commit_editor({"svn:log": message})
+            node = ce.open_root(-1)
+            batons = relpath.split("/")
+            toclose = [node]
+            for i in range(len(batons)):
+                node = node.open_directory("/".join(batons[:i]), -1)
+                toclose.append(node)
+            toclose.append(node.add_directory(relpath, None, -1))
+            for c in reversed(toclose):
+                c.close()
+            ce.close()
         finally:
             self.add_connection(conn)
 
-    def replay(self, revision, low_water_mark, send_deltas, editor, pool=None):
+    def replay(self, revision, low_water_mark, send_deltas, editor):
         conn = self._open_real_transport()
+        self.mutter('svn replay -r%d:%d' % (low_water_mark,revision))
         try:
             return conn.replay(revision, low_water_mark, 
-                                             send_deltas, editor, pool)
+                                             send_deltas, editor)
         finally:
             self.add_connection(conn)
 
-    def do_update(self, revnum, recurse, editor, pool=None):
+    def do_update(self, revnum, recurse, editor):
         conn = self._open_real_transport()
-        conn.set_unbusy_handler(lambda: self.add_connection(conn))
-        return conn.do_update(revnum, recurse, editor, pool)
+        self.mutter('svn do-update -r%d' % (revnum,))
+        return conn.do_update(revnum, "", recurse, editor)
 
     def has_capability(self, cap):
         conn = self.get_connection()
+        self.mutter('svn has-capability %s' % (cap,))
         try:
             return conn.has_capability(cap)
         finally:
             self.add_connection(conn)
 
-    def revprop_list(self, revnum, pool=None):
+    def revprop_list(self, revnum):
         conn = self.get_connection()
+        self.mutter('svn revprop-list -r%d' % (revnum,))
         try:
-            return conn.revprop_list(revnum, pool)
+            return conn.rev_proplist(revnum)
         finally:
             self.add_connection(conn)
 
-    def get_commit_editor(self, revprops, done_cb, lock_token, keep_locks):
+    def get_commit_editor(self, revprops, done_cb=None, 
+                          lock_token=None, keep_locks=False):
         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)
+        self.mutter('svn get-commit-editor %r' % (revprops,))
+        return conn.get_commit_editor(revprops, done_cb, lock_token, keep_locks)
 
     def listable(self):
         """See Transport.listable().
diff --git a/tree.py b/tree.py
index 578bc07ac3da7d685265271ddd5a71bb842ebcc4..ee61ffeb1a4b8d20ae12d5e3202107aeadcddb7c 100644 (file)
--- a/tree.py
+++ b/tree.py
@@ -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
@@ -28,10 +28,8 @@ import md5
 from cStringIO import StringIO
 import urllib
 
-import svn.core, svn.wc, svn.delta
-from svn.core import Pool
-
-from bzrlib.plugins.svn import errors, properties
+from bzrlib.plugins.svn.delta import apply_txdelta_handler
+from bzrlib.plugins.svn import core, errors, wc, properties
 
 def parse_externals_description(base_url, val):
     """Parse an svn:externals property value.
@@ -97,101 +95,100 @@ def inventory_add_external(inv, parent_id, path, revid, ref_revnum, url):
     inv.add(ie)
 
 
-# Deal with Subversion 1.5 and the patched Subversion 1.4 (which are 
-# slightly different).
-
-if hasattr(svn.delta, 'tx_invoke_window_handler'):
-    def apply_txdelta_handler(src_stream, target_stream, pool):
-        assert hasattr(src_stream, 'read')
-        assert hasattr(target_stream, 'write')
-        window_handler, baton = svn.delta.tx_apply(src_stream, target_stream, 
-                                                   None, pool)
-
-        def wrapper(window):
-            window_handler(window, baton)
-
-        return wrapper
-else:
-    def apply_txdelta_handler(src_stream, target_stream, pool):
-        assert hasattr(src_stream, 'read')
-        assert hasattr(target_stream, 'write')
-        ret = svn.delta.svn_txdelta_apply(src_stream, target_stream, None, pool)
-
-        def wrapper(window):
-            svn.delta.invoke_txdelta_window_handler(
-                ret[1], window, ret[2])
-
-        return wrapper
-
-
 class SvnRevisionTree(RevisionTree):
     """A tree that existed in a historical Subversion revision."""
     def __init__(self, repository, revision_id):
         self._repository = repository
         self._revision_id = revision_id
-        pool = Pool()
         (self.branch_path, self.revnum, mapping) = repository.lookup_revision_id(revision_id)
         self._inventory = Inventory()
         self.id_map = repository.get_fileid_map(self.revnum, self.branch_path, 
                                                 mapping)
-        editor = TreeBuildEditor(self, pool)
+        editor = TreeBuildEditor(self)
         self.file_data = {}
         root_repos = repository.transport.get_svn_repos_root()
-        reporter = repository.transport.do_switch(
-                self.revnum, True, 
-                urlutils.join(root_repos, self.branch_path), editor, pool)
-        reporter.set_path("", 0, True, None, pool)
-        reporter.finish_report(pool)
-        pool.destroy()
+        conn = repository.transport.get_connection()
+        reporter = conn.do_switch(
+                self.revnum, "", True, 
+                urlutils.join(root_repos, self.branch_path), editor)
+        try:
+            reporter.set_path("", 0, True)
+            reporter.finish()
+        finally:
+            repository.transport.add_connection(conn)
 
     def get_file_lines(self, file_id):
         return osutils.split_lines(self.file_data[file_id])
 
 
-class TreeBuildEditor(svn.delta.Editor):
+class TreeBuildEditor:
     """Builds a tree given Subversion tree transform calls."""
-    def __init__(self, tree, pool):
+    def __init__(self, tree):
         self.tree = tree
         self.repository = tree._repository
         self.last_revnum = {}
-        self.dir_revnum = {}
-        self.dir_ignores = {}
-        self.pool = pool
 
     def set_target_revision(self, revnum):
         self.revnum = revnum
 
-    def open_root(self, revnum, baton):
+    def open_root(self, revnum):
         file_id, revision_id = self.tree.id_map[""]
         ie = self.tree._inventory.add_path("", 'directory', file_id)
         ie.revision = revision_id
         self.tree._inventory.revision_id = revision_id
-        return file_id
+        return DirectoryTreeEditor(self.tree, file_id)
 
-    def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revnum, pool):
+    def close(self):
+        pass
+
+    def abort(self):
+        pass
+
+class DirectoryTreeEditor:
+    def __init__(self, tree, file_id):
+        self.tree = tree
+        self.file_id = file_id
+
+    def add_directory(self, path, copyfrom_path=None, copyfrom_revnum=-1):
         path = path.decode("utf-8")
         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
+        return DirectoryTreeEditor(self.tree, file_id)
 
-    def change_dir_prop(self, id, name, value, pool):
-        if name == properties.PROP_ENTRY_COMMITTED_REV:
-            self.dir_revnum[id] = int(value)
-        elif name == properties.PROP_IGNORE:
-            self.dir_ignores[id] = value
-        elif name in (properties.PROP_ENTRY_COMMITTED_DATE,
+    def change_prop(self, name, value):
+        if name in (properties.PROP_ENTRY_COMMITTED_DATE,
+                      properties.PROP_ENTRY_COMMITTED_REV,
                       properties.PROP_ENTRY_LAST_AUTHOR,
                       properties.PROP_ENTRY_LOCK_TOKEN,
                       properties.PROP_ENTRY_UUID,
-                      properties.PROP_EXECUTABLE):
+                      properties.PROP_EXECUTABLE,
+                      properties.PROP_IGNORE):
             pass
         elif name.startswith(properties.PROP_WC_PREFIX):
             pass
         elif name.startswith(properties.PROP_PREFIX):
             mutter('unsupported dir property %r', name)
 
-    def change_file_prop(self, id, name, value, pool):
+    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 == properties.PROP_EXECUTABLE:
             self.is_executable = (value != None)
         elif name == properties.PROP_SPECIAL:
@@ -211,22 +208,12 @@ class TreeBuildEditor(svn.delta.Editor):
         elif name.startswith(properties.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:
@@ -253,15 +240,9 @@ class TreeBuildEditor(svn.delta.Editor):
 
         self.file_stream = None
 
-    def close_edit(self):
-        pass
-
-    def abort_edit(self):
-        pass
-
-    def apply_textdelta(self, file_id, base_checksum):
+    def apply_textdelta(self, base_checksum=None):
         self.file_stream = StringIO()
-        return apply_txdelta_handler(StringIO(""), self.file_stream, self.pool)
+        return apply_txdelta_handler("", self.file_stream)
 
 
 class SvnBasisTree(RevisionTree):
@@ -277,10 +258,8 @@ class SvnBasisTree(RevisionTree):
         self._inventory = Inventory(root_id=None)
         self._repository = workingtree.branch.repository
 
-        def add_file_to_inv(relpath, id, revid, wc):
-            props = svn.wc.get_prop_diffs(self.workingtree.abspath(relpath).encode("utf-8"), wc)
-            if isinstance(props, list): # Subversion 1.5
-                props = props[1]
+        def add_file_to_inv(relpath, id, revid, adm):
+            (delta_props, props) = adm.get_prop_diffs(self.workingtree.abspath(relpath))
             if props.has_key(properties.PROP_SPECIAL):
                 ie = self._inventory.add_path(relpath, 'symlink', id)
                 ie.symlink_target = open(self._abspath(relpath)).read()[len("link "):]
@@ -299,14 +278,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.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:
@@ -330,27 +309,27 @@ class SvnBasisTree(RevisionTree):
 
                 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:
                         add_dir_to_inv(subrelpath, subwc, id)
                     finally:
-                        svn.wc.adm_close(subwc)
+                        subwc.close()
                 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(u"", wc, None)
+            add_dir_to_inv(u"", adm, None)
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
 
     def _abspath(self, 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 --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..a1a6755
--- /dev/null
+++ b/util.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <stdbool.h>
+#include <Python.h>
+#include <apr_general.h>
+#include <svn_error.h>
+#include <svn_io.h>
+#include <apr_errno.h>
+#include <svn_error_codes.h>
+
+#include "util.h"
+
+#define BZR_SVN_APR_ERROR_OFFSET (APR_OS_START_USERERR + \
+                                                                 (50 * SVN_ERR_CATEGORY_SIZE))
+
+
+apr_pool_t *Pool()
+{
+    apr_status_t status;
+    apr_pool_t *ret;
+    char errmsg[1024];
+    ret = NULL;
+    status = apr_pool_create(&ret, NULL);
+    if (status != 0) {
+        PyErr_SetString(PyExc_Exception, 
+                                               apr_strerror(status, errmsg, sizeof(errmsg)));
+               return NULL;
+       }
+    return ret;
+}
+
+PyObject *PyErr_NewSubversionException(svn_error_t *error)
+{
+       return Py_BuildValue("(si)", error->message, error->apr_err);
+}
+
+void PyErr_SetSubversionException(svn_error_t *error)
+{
+       PyObject *coremod = PyImport_ImportModule("bzrlib.plugins.svn.core"), *excobj;
+
+       if (coremod == NULL) {
+               return;
+       }
+               
+       excobj = PyObject_GetAttrString(coremod, "SubversionException");
+       
+       if (excobj == NULL) {
+               PyErr_BadInternalCall();
+               return;
+       }
+
+       PyErr_SetObject(excobj, Py_BuildValue("(si)", error->message, error->apr_err));
+}
+
+bool check_error(svn_error_t *error)
+{
+    if (error == NULL)
+               return true;
+
+       if (error->apr_err == BZR_SVN_APR_ERROR_OFFSET)
+               return false; /* Just let Python deal with it */
+
+       PyErr_SetSubversionException(error);
+       return false;
+}
+
+bool string_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **ret)
+{
+       int i;
+    if (l == Py_None) {
+               *ret = NULL;
+        return true;
+       }
+       if (!PyList_Check(l)) {
+               PyErr_Format(PyExc_TypeError, "Expected list of strings, got: %s",
+                                        l->ob_type->tp_name);
+               return false;
+       }
+    *ret = apr_array_make(pool, PyList_Size(l), sizeof(char *));
+       for (i = 0; i < PyList_GET_SIZE(l); i++) {
+               PyObject *item = PyList_GET_ITEM(l, i);
+               if (!PyString_Check(item)) {
+                       PyErr_SetString(PyExc_TypeError, "Expected list of strings");
+                       return false;
+               }
+               APR_ARRAY_PUSH(*ret, char *) = apr_pstrdup(pool, PyString_AsString(item));
+       }
+    return true;
+}
+
+PyObject *prop_hash_to_dict(apr_hash_t *props)
+{
+    const char *key;
+    apr_hash_index_t *idx;
+    apr_ssize_t klen;
+    svn_string_t *val;
+    apr_pool_t *pool;
+       PyObject *py_props;
+    if (props == NULL) {
+        Py_RETURN_NONE;
+       }
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    py_props = PyDict_New();
+    for (idx = apr_hash_first(pool, props); idx != NULL; 
+                idx = apr_hash_next(idx)) {
+               PyObject *py_val;
+        apr_hash_this(idx, (const void **)&key, &klen, (void **)&val);
+               if (val == NULL || val->data == NULL)
+                       py_val = Py_None;
+               else
+                       py_val = PyString_FromStringAndSize(val->data, val->len);
+        PyDict_SetItemString(py_props, key, py_val);
+       }
+    apr_pool_destroy(pool);
+    return py_props;
+}
+
+svn_error_t *py_svn_log_wrapper(void *baton, apr_hash_t *changed_paths, long revision, const char *author, const char *date, const char *message, apr_pool_t *pool)
+{
+    apr_hash_index_t *idx;
+    const char *key;
+    apr_ssize_t klen;
+    svn_log_changed_path_t *val;
+       PyObject *revprops, *py_changed_paths, *ret;
+
+    if (changed_paths == NULL) {
+        py_changed_paths = Py_None;
+       } else {
+        py_changed_paths = PyDict_New();
+        for (idx = apr_hash_first(pool, changed_paths); idx != NULL;
+             idx = apr_hash_next(idx)) {
+            apr_hash_this(idx, (const void **)&key, &klen, (void **)&val);
+                       PyDict_SetItemString(py_changed_paths, key, 
+                                       Py_BuildValue("(czi)", val->action, val->copyfrom_path, 
+                                         val->copyfrom_rev));
+               }
+       }
+    revprops = PyDict_New();
+    if (message != NULL) {
+        PyDict_SetItemString(revprops, SVN_PROP_REVISION_LOG, 
+                                                        PyString_FromString(message));
+       }
+    if (author != NULL) {
+        PyDict_SetItemString(revprops, SVN_PROP_REVISION_AUTHOR, 
+                                                        PyString_FromString(author));
+       }
+    if (date != NULL) {
+        PyDict_SetItemString(revprops, SVN_PROP_REVISION_DATE, 
+                                                        PyString_FromString(date));
+       }
+    ret = PyObject_CallFunction((PyObject *)baton, "OiO", py_changed_paths, 
+                                                                revision, revprops);
+       if (ret == NULL)
+               return py_svn_error();
+       return NULL;
+}
+
+svn_error_t *py_svn_error()
+{
+       return svn_error_create(BZR_SVN_APR_ERROR_OFFSET, NULL, "Error occured in python bindings");
+}
+
+PyObject *wrap_lock(svn_lock_t *lock)
+{
+    return Py_BuildValue("(zzzbzz)", lock->path, lock->token, lock->owner, 
+                                                lock->comment, lock->is_dav_comment, 
+                                                lock->creation_date, lock->expiration_date);
+}
+
+apr_array_header_t *revnum_list_to_apr_array(apr_pool_t *pool, PyObject *l)
+{
+       int i;
+    apr_array_header_t *ret;
+    if (l == Py_None) {
+        return NULL;
+       }
+    ret = apr_array_make(pool, PyList_Size(l), sizeof(svn_revnum_t));
+       if (ret == NULL) {
+               PyErr_NoMemory();
+               return NULL;
+       }
+    for (i = 0; i < PyList_Size(l); i++) {
+               APR_ARRAY_PUSH(ret, svn_revnum_t) = PyLong_AsLong(PyList_GetItem(l, i));
+       }
+    return ret;
+}
+
+
+static svn_error_t *py_stream_read(void *baton, char *buffer, apr_size_t *length)
+{
+    PyObject *self = (PyObject *)baton, *ret;
+    ret = PyObject_CallMethod(self, "read", "i", *length);
+       if (ret == NULL)
+               return py_svn_error();
+       /* FIXME: Handle !PyString_Check(ret) */
+    *length = PyString_Size(ret);
+    memcpy(buffer, PyString_AS_STRING(ret), *length);
+    return NULL;
+}
+
+static svn_error_t *py_stream_write(void *baton, const char *data, apr_size_t *len)
+{
+    PyObject *self = (PyObject *)baton, *ret;
+    ret = PyObject_CallMethod(self, "write", "s#", data, len[0]);
+       if (ret == NULL)
+               return py_svn_error();
+       return NULL;
+}
+
+static svn_error_t *py_stream_close(void *baton)
+{
+    PyObject *self = (PyObject *)baton, *ret;
+    ret = PyObject_CallMethod(self, "close", NULL);
+       if (ret == NULL)
+               return py_svn_error();
+    Py_DECREF(self);
+       return NULL;
+}
+
+svn_stream_t *new_py_stream(apr_pool_t *pool, PyObject *py)
+{
+    svn_stream_t *stream;
+    Py_INCREF(py);
+    stream = svn_stream_create((void *)py, pool);
+    svn_stream_set_read(stream, py_stream_read);
+    svn_stream_set_write(stream, py_stream_write);
+    svn_stream_set_close(stream, py_stream_close);
+    return stream;
+}
+
+svn_error_t *py_cancel_func(void *cancel_baton)
+{
+       PyObject *py_fn = (PyObject *)cancel_baton;
+    if (py_fn != Py_None) {
+        PyObject *ret = PyObject_CallFunction(py_fn, NULL);
+               if (PyBool_Check(ret) && ret == Py_True) {
+            return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+               }
+       }
+    return NULL;
+}
+
+
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..b7d8dab
--- /dev/null
+++ b/util.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _BZR_SVN_UTIL_H_
+#define _BZR_SVN_UTIL_H_
+
+#pragma GCC visibility push(hidden)
+
+__attribute__((warn_unused_result)) apr_pool_t *Pool(void);
+__attribute__((warn_unused_result)) bool check_error(svn_error_t *error);
+bool string_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **);
+PyObject *prop_hash_to_dict(apr_hash_t *props);
+svn_error_t *py_svn_log_wrapper(void *baton, apr_hash_t *changed_paths, 
+                                                               long revision, const char *author, 
+                                                               const char *date, const char *message, 
+                                                               apr_pool_t *pool);
+svn_error_t *py_svn_error(void);
+void PyErr_SetSubversionException(svn_error_t *error);
+
+#define RUN_SVN_WITH_POOL(pool, cmd)  \
+       if (!check_error((cmd))) { \
+               apr_pool_destroy(pool); \
+               return NULL; \
+       }
+
+PyObject *wrap_lock(svn_lock_t *lock);
+apr_array_header_t *revnum_list_to_apr_array(apr_pool_t *pool, PyObject *l);
+svn_stream_t *new_py_stream(apr_pool_t *pool, PyObject *py);
+PyObject *PyErr_NewSubversionException(svn_error_t *error);
+svn_error_t *py_cancel_func(void *cancel_baton);
+
+#pragma GCC visibility pop
+
+#endif /* _BZR_SVN_UTIL_H_ */
diff --git a/wc.c b/wc.c
new file mode 100644 (file)
index 0000000..16d3381
--- /dev/null
+++ b/wc.c
@@ -0,0 +1,824 @@
+/*
+ * Copyright Â© 2008 Jelmer Vernooij <jelmer@samba.org>
+ * -*- coding: utf-8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <Python.h>
+#include <apr_general.h>
+#include <svn_wc.h>
+#include <structmember.h>
+#include <stdbool.h>
+
+#include "util.h"
+#include "editor.h"
+
+PyAPI_DATA(PyTypeObject) Entry_Type;
+PyAPI_DATA(PyTypeObject) Adm_Type;
+
+static PyObject *py_entry(const svn_wc_entry_t *entry);
+
+static svn_error_t *py_ra_report_set_path(void *baton, const char *path, long revision, int start_empty, const char *lock_token, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)baton, *py_lock_token, *ret;
+    if (lock_token == NULL) {
+        py_lock_token = Py_None;
+       } else {
+        py_lock_token = PyString_FromString(lock_token);
+       }
+       ret = PyObject_CallFunction(self, "set_path", "slbO", path, revision, start_empty, py_lock_token);
+       if (ret == NULL)
+               return py_svn_error();
+    return NULL;
+}
+
+static svn_error_t *py_ra_report_delete_path(void *baton, const char *path, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)baton, *ret;
+       ret = PyObject_CallFunction(self, "delete_path", "s", path);
+       if (ret == NULL)
+               return py_svn_error();
+    return NULL;
+}
+
+static svn_error_t *py_ra_report_link_path(void *report_baton, const char *path, const char *url, long revision, int start_empty, const char *lock_token, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token;
+    if (lock_token == NULL) {
+        py_lock_token = Py_None;
+       } else { 
+        py_lock_token = PyString_FromString(lock_token);
+       }
+       ret = PyObject_CallFunction(self, "link_path", "sslbO", path, url, revision, start_empty, py_lock_token);
+       if (ret == NULL)
+               return py_svn_error();
+    return NULL;
+}
+
+static svn_error_t *py_ra_report_finish(void *baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)baton, *ret;
+       ret = PyObject_CallFunction(self, "finish", NULL);
+       if (ret == NULL)
+               return py_svn_error();
+    return NULL;
+}
+
+static svn_error_t *py_ra_report_abort(void *baton, apr_pool_t *pool)
+{
+    PyObject *self = (PyObject *)baton, *ret;
+       ret = PyObject_CallFunction(self, "abort", NULL);
+       if (ret == NULL)
+               return py_svn_error();
+    return NULL;
+}
+
+static const svn_ra_reporter2_t py_ra_reporter = {
+       .finish_report = py_ra_report_finish,
+       .abort_report = py_ra_report_abort,
+       .link_path = py_ra_report_link_path,
+       .delete_path = py_ra_report_delete_path,
+       .set_path = py_ra_report_set_path,
+};
+
+
+
+/**
+ * Get libsvn_wc version information.
+ *
+ * :return: tuple with major, minor, patch version number and tag.
+ */
+static PyObject *version(PyObject *self)
+{
+    const svn_version_t *ver = svn_wc_version();
+    return Py_BuildValue("(iiis)", ver->major, ver->minor, 
+                                                ver->patch, ver->tag);
+}
+
+static svn_error_t *py_wc_found_entry(const char *path, const svn_wc_entry_t *entry, void *walk_baton, apr_pool_t *pool)
+{
+    PyObject *fn = (PyObject *)walk_baton, *ret;
+       ret = PyObject_CallFunction(fn, "sO", path, py_entry(entry));
+       if (ret == NULL)
+               return py_svn_error();
+    return NULL;
+}
+
+static svn_wc_entry_callbacks_t py_wc_entry_callbacks = {
+       .found_entry = py_wc_found_entry
+};
+
+static void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool)
+{
+    /* FIXME */
+}
+
+typedef struct {
+       PyObject_HEAD
+       apr_pool_t *pool;
+       svn_wc_entry_t entry;
+} EntryObject;
+
+static void entry_dealloc(PyObject *self)
+{
+       apr_pool_destroy(((EntryObject *)self)->pool);
+       PyObject_Del(self);
+}
+
+static PyMemberDef entry_members[] = {
+       { "copyfrom_url", T_STRING, offsetof(EntryObject, entry.copyfrom_url), READONLY, NULL },
+       { "url", T_STRING, offsetof(EntryObject, entry.url), READONLY, NULL },
+       { "repos", T_STRING, offsetof(EntryObject, entry.repos), READONLY, NULL },
+       { "schedule", T_INT, offsetof(EntryObject, entry.schedule), READONLY, NULL },
+       { "kind", T_INT, offsetof(EntryObject, entry.kind), READONLY, NULL },
+       { "revision", T_LONG, offsetof(EntryObject, entry.revision), READONLY, NULL },
+       { "cmt_rev", T_LONG, offsetof(EntryObject, entry.cmt_rev), READONLY, NULL },
+       { NULL, }
+};
+
+PyTypeObject Entry_Type = {
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "wc.Entry",
+       .tp_basicsize = sizeof(EntryObject),
+       .tp_dealloc = entry_dealloc,
+       .tp_members = entry_members,
+};
+
+static PyObject *py_entry(const svn_wc_entry_t *entry)
+{
+       EntryObject *ret = PyObject_New(EntryObject, &Entry_Type);
+       if (ret == NULL)
+               return NULL;
+
+       ret->pool = Pool();
+       if (ret->pool == NULL)
+               return NULL;
+       ret->entry = *svn_wc_entry_dup(entry, ret->pool);
+    return (PyObject *)ret;
+}
+
+typedef struct {
+       PyObject_HEAD
+    svn_wc_adm_access_t *adm;
+    apr_pool_t *pool;
+} AdmObject;
+
+static PyObject *adm_init(PyTypeObject *self, PyObject *args, PyObject *kwargs)
+{
+       PyObject *associated;
+       char *path;
+       bool write_lock=false;
+       int depth=0;
+       PyObject *cancel_func=Py_None;
+       svn_wc_adm_access_t *parent_wc;
+       AdmObject *ret;
+       char *kwnames[] = { "associated", "path", "write_lock", "depth", "cancel_func", NULL };
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|biO", kwnames, &associated, &path, &write_lock, &depth, &cancel_func))
+               return NULL;
+
+       ret = PyObject_New(AdmObject, &Adm_Type);
+       if (ret == NULL)
+               return NULL;
+
+       ret->pool = Pool();
+       if (ret->pool == NULL)
+               return NULL;
+       if (associated == Py_None) {
+               parent_wc = NULL;
+       } else {
+               parent_wc = ((AdmObject *)associated)->adm;
+       }
+       if (!check_error(svn_wc_adm_open3(&ret->adm, parent_wc, path, 
+                     write_lock, depth, py_cancel_func, cancel_func, 
+                     ret->pool)))
+               return NULL;
+
+       return (PyObject *)ret;
+}
+
+static PyObject *adm_access_path(PyObject *self)
+{
+       AdmObject *admobj = (AdmObject *)self;
+       return PyString_FromString(svn_wc_adm_access_path(admobj->adm));
+}
+
+static PyObject *adm_locked(PyObject *self)
+{
+       AdmObject *admobj = (AdmObject *)self;
+       return PyBool_FromLong(svn_wc_adm_locked(admobj->adm));
+}
+
+static PyObject *adm_prop_get(PyObject *self, PyObject *args)
+{
+       char *name, *path;
+       AdmObject *admobj = (AdmObject *)self;
+       const svn_string_t *value;
+       apr_pool_t *temp_pool;
+       PyObject *ret;
+
+       if (!PyArg_ParseTuple(args, "ss", &name, &path))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_get(&value, name, path, admobj->adm, temp_pool));
+       if (value == NULL || value->data == NULL) {
+               ret = Py_None;
+       } else {
+               ret = PyString_FromStringAndSize(value->data, value->len);
+       }
+       apr_pool_destroy(temp_pool);
+       return ret;
+}
+
+static PyObject *adm_prop_set(PyObject *self, PyObject *args)
+{
+       char *name, *value, *path; 
+       AdmObject *admobj = (AdmObject *)self;
+       bool skip_checks=false;
+       apr_pool_t *temp_pool;
+       int vallen;
+       svn_string_t *cvalue;
+
+       if (!PyArg_ParseTuple(args, "ss#s|b", &name, &value, &vallen, &path, &skip_checks))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       cvalue = svn_string_ncreate(value, vallen, temp_pool);
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set2(name, cvalue, path, admobj->adm, 
+                               skip_checks, temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *adm_entries_read(PyObject *self, PyObject *args)
+{
+       apr_hash_t *entries;
+       AdmObject *admobj = (AdmObject *)self;
+       apr_pool_t *temp_pool;
+       bool show_hidden=false;
+       apr_hash_index_t *idx;
+       const char *key;
+       apr_ssize_t klen;
+       svn_wc_entry_t *entry;
+       PyObject *py_entries;
+
+       if (!PyArg_ParseTuple(args, "|b", &show_hidden))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_entries_read(&entries, admobj->adm, 
+                                show_hidden, temp_pool));
+       py_entries = PyDict_New();
+       idx = apr_hash_first(temp_pool, entries);
+       while (idx != NULL) {
+               apr_hash_this(idx, (const void **)&key, &klen, (void **)&entry);
+               PyDict_SetItemString(py_entries, key, py_entry(entry));
+               idx = apr_hash_next(idx);
+       }
+       apr_pool_destroy(temp_pool);
+       return py_entries;
+}
+
+static PyObject *adm_walk_entries(PyObject *self, PyObject *args)
+{
+       char *path;
+       PyObject *callbacks; 
+       bool show_hidden=false;
+       PyObject *cancel_func=Py_None;
+       apr_pool_t *temp_pool;
+       AdmObject *admobj = (AdmObject *)self;
+
+       if (!PyArg_ParseTuple(args, "sO|bO", &path, &callbacks, &show_hidden, &cancel_func))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries2(path, admobj->adm, 
+                               &py_wc_entry_callbacks, (void *)callbacks,
+                               show_hidden, py_cancel_func, (void *)cancel_func,
+                               temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *adm_entry(PyObject *self, PyObject *args)
+{
+       char *path;
+       bool show_hidden=false;
+       apr_pool_t *temp_pool;
+       AdmObject *admobj = (AdmObject *)self;
+       const svn_wc_entry_t *entry;
+
+       if (!PyArg_ParseTuple(args, "s|b", &path, &show_hidden))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_entry(&entry, path, admobj->adm, show_hidden, temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       return py_entry(entry);
+}
+
+static PyObject *adm_get_prop_diffs(PyObject *self, PyObject *args)
+{
+       char *path;
+       apr_pool_t *temp_pool;
+       apr_array_header_t *propchanges;
+       apr_hash_t *original_props;
+       AdmObject *admobj = (AdmObject *)self;
+       svn_prop_t *el;
+       int i;
+       PyObject *py_propchanges, *py_orig_props;
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_prop_diffs(&propchanges, &original_props, 
+                               path, admobj->adm, temp_pool));
+       py_propchanges = PyList_New(propchanges->nelts);
+       for (i = 0; i < propchanges->nelts; i++) {
+               el = APR_ARRAY_IDX(propchanges, i, svn_prop_t *);
+               PyList_SetItem(py_propchanges, i, 
+                                          Py_BuildValue("(ss#)", el->name, el->value->data, el->value->len));
+       }
+       py_orig_props = prop_hash_to_dict(original_props);
+       apr_pool_destroy(temp_pool);
+       return Py_BuildValue("(OO)", py_propchanges, py_orig_props);
+}
+
+static PyObject *adm_add(PyObject *self, PyObject *args)
+{
+       char *path, *copyfrom_url=NULL;
+       svn_revnum_t copyfrom_rev=-1; 
+       PyObject *cancel_func=Py_None, *notify_func=Py_None;
+       AdmObject *admobj = (AdmObject *)self;
+       apr_pool_t *temp_pool;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+
+       if (!PyArg_ParseTuple(args, "s|zlOO", &path, &copyfrom_url, &copyfrom_rev, &cancel_func, &notify_func))
+               return NULL;
+
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_add2(path, admobj->adm, copyfrom_url, 
+                                                       copyfrom_rev, py_cancel_func, 
+                                                       (void *)cancel_func,
+                                                       py_wc_notify_func, 
+                                                       (void *)notify_func, 
+                                                       temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *adm_copy(PyObject *self, PyObject *args)
+{
+       AdmObject *admobj = (AdmObject *)self;
+       char *src, *dst; 
+       PyObject *cancel_func=Py_None, *notify_func=Py_None;
+       apr_pool_t *temp_pool;
+
+       if (!PyArg_ParseTuple(args, "ss|OO", &src, &dst, &cancel_func, &notify_func))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_copy2(src, admobj->adm, dst,
+                                                       py_cancel_func, (void *)cancel_func,
+                                                       py_wc_notify_func, (void *)notify_func, 
+                                                       temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *adm_delete(PyObject *self, PyObject *args)
+{
+       AdmObject *admobj = (AdmObject *)self;
+       apr_pool_t *temp_pool;
+       char *path;
+       PyObject *cancel_func=Py_None, *notify_func=Py_None;
+
+       if (!PyArg_ParseTuple(args, "s|OO", &path, &cancel_func, &notify_func))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete2(path, admobj->adm, 
+                                                       py_cancel_func, (void *)cancel_func,
+                                                       py_wc_notify_func, (void *)notify_func, 
+                                                       temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *adm_crawl_revisions(PyObject *self, PyObject *args)
+{
+       char *path;
+       PyObject *reporter;
+       bool restore_files=true, recurse=true, use_commit_times=true;
+       PyObject *notify_func=Py_None;
+       apr_pool_t *temp_pool;
+       AdmObject *admobj = (AdmObject *)self;
+       svn_wc_traversal_info_t *traversal_info;
+
+       if (!PyArg_ParseTuple(args, "sO|bbbO", &path, &reporter, &restore_files, &recurse, &use_commit_times,
+                                                 &notify_func))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+       traversal_info = svn_wc_init_traversal_info(temp_pool);
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions2(path, admobj->adm, 
+                               &py_ra_reporter, (void *)reporter, 
+                               restore_files, recurse, use_commit_times, 
+                               py_wc_notify_func, (void *)notify_func,
+                               traversal_info, temp_pool));
+       apr_pool_destroy(temp_pool);
+
+       Py_RETURN_NONE;
+}
+
+static PyObject *adm_get_update_editor(PyObject *self, PyObject *args)
+{
+       char *target;
+       bool use_commit_times=true, recurse=true;
+       PyObject * notify_func=Py_None, *cancel_func=Py_None;
+       char *diff3_cmd=NULL;
+       const svn_delta_editor_t *editor;
+       AdmObject *admobj = (AdmObject *)self;
+       void *edit_baton;
+       apr_pool_t *pool;
+       svn_revnum_t *latest_revnum;
+
+       if (!PyArg_ParseTuple(args, "s|bbOOz", &target, &use_commit_times, &recurse, &notify_func, &cancel_func, &diff3_cmd))
+               return NULL;
+
+       pool = Pool();
+       if (pool == NULL)
+               return NULL;
+       latest_revnum = (svn_revnum_t *)apr_palloc(pool, sizeof(svn_revnum_t));
+       if (!check_error(svn_wc_get_update_editor2(latest_revnum, admobj->adm, target, 
+                               use_commit_times, recurse, py_wc_notify_func, (void *)notify_func, 
+                               py_cancel_func, (void *)cancel_func, diff3_cmd, &editor, &edit_baton, 
+                               NULL, pool))) {
+               apr_pool_destroy(pool);
+               return NULL;
+       }
+       return new_editor_object(editor, edit_baton, pool, &Editor_Type, NULL, NULL);
+}
+
+static bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_array_header_t **ret)
+{
+       PyObject *key, *val;
+       Py_ssize_t idx;
+
+       if (dict == Py_None) {
+               *ret = NULL;
+               return true;
+       }
+
+       if (!PyDict_Check(dict)) {
+               PyErr_SetString(PyExc_TypeError, "Expected dictionary with property changes");
+               return false;
+       }
+
+       *ret = apr_array_make(pool, PyDict_Size(dict), sizeof(char *));
+
+       while (PyDict_Next(dict, &idx, &key, &val)) {
+                  svn_prop_t *prop = apr_palloc(pool, sizeof(svn_prop_t));
+                  prop->name = PyString_AsString(key);
+                  if (val == Py_None) {
+                          prop->value = NULL;
+                  } else {
+                      prop->value = svn_string_ncreate(PyString_AsString(val), PyString_Size(val), pool);
+                  }
+                  APR_ARRAY_PUSH(*ret, svn_prop_t *) = prop;
+       }
+
+       return true;
+}
+
+static PyObject *adm_process_committed(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+       char *path, *rev_date, *rev_author;
+       bool recurse, remove_lock = false;
+       unsigned char *digest = NULL;
+       svn_revnum_t new_revnum;
+       PyObject *py_wcprop_changes = Py_None;
+       apr_array_header_t *wcprop_changes;
+       AdmObject *admobj = (AdmObject *)self;
+       apr_pool_t *temp_pool;
+       char *kwnames[] = { "path", "recurse", "new_revnum", "rev_date", "rev_author", 
+                               "wcprop_changes", "remove_lock", "digest", NULL };
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sblss|Obs", kwnames, 
+                                                                        &path, &recurse, &new_revnum, &rev_date,
+                                                                        &rev_author, &py_wcprop_changes, 
+                                                                        &remove_lock, &digest))
+               return NULL;
+
+       temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+
+       if (!py_dict_to_wcprop_changes(py_wcprop_changes, temp_pool, &wcprop_changes)) {
+               apr_pool_destroy(temp_pool);
+               return NULL;
+       }
+
+       RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(path, admobj->adm, recurse, new_revnum, 
+                                                                                                                  rev_date, rev_author, wcprop_changes, 
+                                                                                                                  remove_lock, digest, temp_pool));
+
+       apr_pool_destroy(temp_pool);
+
+       return Py_None;
+}
+
+static PyObject *adm_close(PyObject *self)
+{
+       AdmObject *admobj = (AdmObject *)self;
+       if (admobj->adm != NULL) {
+               svn_wc_adm_close(admobj->adm);
+               admobj->adm = NULL;
+       }
+
+       Py_RETURN_NONE;
+}
+
+static void adm_dealloc(PyObject *self)
+{
+       apr_pool_destroy(((AdmObject *)self)->pool);
+       PyObject_Del(self);
+}
+
+static PyMethodDef adm_methods[] = { 
+       { "prop_set", adm_prop_set, METH_VARARGS, NULL },
+       { "access_path", (PyCFunction)adm_access_path, METH_NOARGS, NULL },
+       { "prop_get", adm_prop_get, METH_VARARGS, NULL },
+       { "entries_read", adm_entries_read, METH_VARARGS, NULL },
+       { "walk_entries", adm_walk_entries, METH_VARARGS, NULL },
+       { "locked", (PyCFunction)adm_locked, METH_NOARGS, NULL },
+       { "get_prop_diffs", adm_get_prop_diffs, METH_VARARGS, NULL },
+       { "add", adm_add, METH_VARARGS, NULL },
+       { "copy", adm_copy, METH_VARARGS, NULL },
+       { "delete", adm_delete, METH_VARARGS, NULL },
+       { "crawl_revisions", adm_crawl_revisions, METH_VARARGS, NULL },
+       { "get_update_editor", adm_get_update_editor, METH_VARARGS, NULL },
+       { "close", (PyCFunction)adm_close, METH_NOARGS, NULL },
+       { "entry", (PyCFunction)adm_entry, METH_VARARGS, NULL },
+       { "process_committed", (PyCFunction)adm_process_committed, METH_VARARGS|METH_KEYWORDS, NULL },
+       { NULL, }
+};
+
+PyTypeObject Adm_Type = {
+       PyObject_HEAD_INIT(&PyType_Type) 0,
+       .tp_name = "wc.WorkingCopy",
+       .tp_basicsize = sizeof(AdmObject),
+       .tp_new = adm_init,
+       .tp_dealloc = adm_dealloc,
+       .tp_methods = adm_methods,
+};
+
+/** 
+ * Determine the revision status of a specified working copy.
+ *
+ * :return: Tuple with minimum and maximum revnums found, whether the 
+ * working copy was switched and whether it was modified.
+ */
+static PyObject *revision_status(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+       char *kwnames[] = { "wc_path", "trail_url", "committed", "cancel_func", NULL };
+       char *wc_path, *trail_url=NULL;
+       bool committed=false;
+       PyObject *cancel_func=Py_None, *ret;
+     svn_wc_revision_status_t *revstatus;
+    apr_pool_t *temp_pool;
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|zbO", kwnames, &wc_path, &trail_url, &committed, 
+                                                 &cancel_func))
+               return NULL;
+
+    temp_pool = Pool();
+       if (temp_pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(temp_pool, svn_wc_revision_status(&revstatus, wc_path, trail_url,
+                 committed, py_cancel_func, cancel_func, temp_pool));
+    ret = Py_BuildValue("(llbb)", revstatus->min_rev, revstatus->max_rev, 
+            revstatus->switched, revstatus->modified);
+    apr_pool_destroy(temp_pool);
+    return ret;
+}
+
+static PyObject *is_normal_prop(PyObject *self, PyObject *args)
+{
+       char *name;
+
+       if (!PyArg_ParseTuple(args, "s", &name))
+               return NULL;
+
+    return PyBool_FromLong(svn_wc_is_normal_prop(name));
+}
+
+static PyObject *is_wc_prop(PyObject *self, PyObject *args)
+{
+       char *name;
+
+       if (!PyArg_ParseTuple(args, "s", &name))
+               return NULL;
+
+    return PyBool_FromLong(svn_wc_is_wc_prop(name));
+}
+
+static PyObject *is_entry_prop(PyObject *self, PyObject *args)
+{
+       char *name;
+
+       if (!PyArg_ParseTuple(args, "s", &name))
+               return NULL;
+
+    return PyBool_FromLong(svn_wc_is_entry_prop(name));
+}
+
+static PyObject *get_adm_dir(PyObject *self)
+{
+    apr_pool_t *pool;
+       PyObject *ret;
+       const char *dir;
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    dir = svn_wc_get_adm_dir(pool);
+       ret = PyString_FromString(dir);
+    apr_pool_destroy(pool);
+    return ret;
+}
+
+static PyObject *get_pristine_copy_path(PyObject *self, PyObject *args)
+{
+    apr_pool_t *pool;
+    const char *pristine_path;
+       char *path;
+       PyObject *ret;
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(pool, svn_wc_get_pristine_copy_path(path, &pristine_path, pool));
+       ret = PyString_FromString(pristine_path);
+       apr_pool_destroy(pool);
+    return ret;
+}
+
+static PyObject *get_default_ignores(PyObject *self, PyObject *args)
+{
+    apr_array_header_t *patterns;
+    apr_pool_t *pool;
+    apr_hash_t *hash_config;
+       apr_ssize_t idx = 0;
+       int i = 0;
+       PyObject *pyk, *pyv, *config;
+       PyObject *ret;
+
+       if (!PyArg_ParseTuple(args, "O", &config))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    hash_config = apr_hash_make(pool);
+       while (PyDict_Next(config, &idx, &pyk, &pyv))
+        apr_hash_set(hash_config, (char *)PyString_AsString(pyk), PyString_Size(pyk), (char *)PyString_AsString(pyv));
+    RUN_SVN_WITH_POOL(pool, svn_wc_get_default_ignores(&patterns, hash_config, pool));
+    ret = PyList_New(patterns->nelts);
+       for (i = 0; i < patterns->nelts; i++) {
+               PyList_SetItem(ret, i, PyString_FromString(APR_ARRAY_IDX(patterns, i, char *)));
+       }
+    apr_pool_destroy(pool);
+    return ret;
+}
+
+static PyObject *ensure_adm(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+       char *path, *uuid, *url;
+       char *repos=NULL; 
+       svn_revnum_t rev=-1;
+    apr_pool_t *pool;
+       char *kwnames[] = { "path", "uuid", "url", "repos", "rev", NULL };
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss|sl", kwnames, 
+                                                                        &path, &uuid, &url, &repos, &rev))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+    RUN_SVN_WITH_POOL(pool, 
+                                         svn_wc_ensure_adm2(path, uuid, url, repos, rev, pool));
+    apr_pool_destroy(pool);
+       Py_RETURN_NONE;
+}
+
+static PyObject *check_wc(PyObject *self, PyObject *args)
+{
+       char *path;
+    apr_pool_t *pool;
+    int wc_format;
+
+       if (!PyArg_ParseTuple(args, "s", &path))
+               return NULL;
+
+    pool = Pool();
+       if (pool == NULL)
+               return NULL;
+       RUN_SVN_WITH_POOL(pool, svn_wc_check_wc(path, &wc_format, pool));
+    apr_pool_destroy(pool);
+    return PyLong_FromLong(wc_format);
+}
+
+static PyMethodDef wc_methods[] = {
+       { "check_wc", check_wc, METH_VARARGS, NULL },
+       { "ensure_adm", (PyCFunction)ensure_adm, METH_KEYWORDS|METH_VARARGS, NULL },
+       { "get_default_ignores", get_default_ignores, METH_VARARGS, NULL },
+       { "get_adm_dir", (PyCFunction)get_adm_dir, METH_NOARGS, NULL },
+       { "get_pristine_copy_path", get_pristine_copy_path, METH_VARARGS, NULL },
+       { "is_normal_prop", is_normal_prop, METH_VARARGS, NULL },
+       { "is_entry_prop", is_entry_prop, METH_VARARGS, NULL },
+       { "is_wc_prop", is_wc_prop, METH_VARARGS, NULL },
+       { "revision_status", (PyCFunction)revision_status, METH_KEYWORDS|METH_VARARGS, NULL },
+       { "version", (PyCFunction)version, METH_NOARGS, NULL },
+       { NULL, }
+};
+
+void initwc(void)
+{
+       PyObject *mod;
+
+       if (PyType_Ready(&Entry_Type) < 0)
+               return;
+
+       if (PyType_Ready(&Adm_Type) < 0)
+               return;
+
+       if (PyType_Ready(&Editor_Type) < 0)
+               return;
+
+       if (PyType_Ready(&FileEditor_Type) < 0)
+               return;
+
+       if (PyType_Ready(&DirectoryEditor_Type) < 0)
+               return;
+
+       if (PyType_Ready(&TxDeltaWindowHandler_Type) < 0)
+               return;
+
+       apr_initialize();
+
+       mod = Py_InitModule3("wc", wc_methods, "Working Copies");
+       if (mod == NULL)
+               return;
+
+       PyModule_AddIntConstant(mod, "SCHEDULE_NORMAL", 0);
+       PyModule_AddIntConstant(mod, "SCHEDULE_ADD", 1);
+       PyModule_AddIntConstant(mod, "SCHEDULE_DELETE", 2);
+       PyModule_AddIntConstant(mod, "SCHEDULE_REPLACE", 3);
+
+       PyModule_AddObject(mod, "WorkingCopy", (PyObject *)&Adm_Type);
+       Py_INCREF(&Adm_Type);
+}
index e048f5a95ff5dd79ac70f631b5ff9f36e4a6ecd6..ca93fcf56ef0835479c3e8289135b22ecfc6889b 100644 (file)
 """Checkouts and working trees (working copies)."""
 
 import bzrlib, bzrlib.add
-from bzrlib import urlutils
+from bzrlib import osutils, urlutils, ignores
 from bzrlib.branch import PullResult
 from bzrlib.bzrdir import BzrDirFormat, BzrDir
 from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
                            NoRepositoryPresent, BzrError, UninitializableFormat,
-                           OutOfDateTree)
+                           OutOfDateTree, UnsupportedFormatError)
 from bzrlib.inventory import Inventory, InventoryFile, InventoryLink
 from bzrlib.lockable_files import TransportLock, LockableFiles
 from bzrlib.lockdir import LockDir
-from bzrlib.osutils import file_kind, fingerprint_file, supports_executable
 from bzrlib.revision import NULL_REVISION
 from bzrlib.trace import mutter
 from bzrlib.revisiontree import RevisionTree
@@ -36,7 +35,7 @@ from bzrlib.plugins.svn import properties
 from bzrlib.plugins.svn.branch import SvnBranch
 from bzrlib.plugins.svn.commit import _revision_id_to_svk_feature
 from bzrlib.plugins.svn.convert import SvnConverter
-from bzrlib.plugins.svn.errors import LocalCommitsUnsupported, NoSvnRepositoryPresent, ERR_FS_TXN_OUT_OF_DATE, ERR_ENTRY_EXISTS, ERR_WC_PATH_NOT_FOUND, ERR_WC_NOT_DIRECTORY
+from bzrlib.plugins.svn.errors import NoSvnRepositoryPresent, ERR_FS_TXN_OUT_OF_DATE, ERR_ENTRY_EXISTS, ERR_WC_PATH_NOT_FOUND, ERR_WC_NOT_DIRECTORY, ERR_WC_UNSUPPORTED_FORMAT
 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)
@@ -44,17 +43,15 @@ from bzrlib.plugins.svn.remote import SvnRemoteAccess
 from bzrlib.plugins.svn.repository import SvnRepository
 from bzrlib.plugins.svn.svk import SVN_PROP_SVK_MERGE, parse_svk_features, serialize_svk_features
 from bzrlib.plugins.svn.mapping import escape_svn_path
-from bzrlib.plugins.svn.transport import (SvnRaTransport, bzr_to_svn_url, create_svn_client,
-                       svn_config) 
+from bzrlib.plugins.svn.transport import (SvnRaTransport, bzr_to_svn_url, svn_config) 
 from bzrlib.plugins.svn.tree import SvnBasisTree
 
 import os
 import urllib
 
-import svn.core, svn.wc
-from svn.core import SubversionException
+import core, wc
+from core import SubversionException, time_to_cstring
 
-from bzrlib.plugins.svn.errors import NoCheckoutSupport
 from bzrlib.plugins.svn.format import get_rich_root_format
 
 def generate_ignore_list(ignore_map):
@@ -74,101 +71,121 @@ 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()
-        status = svn.wc.revision_status(self.basedir, None, True, None, None)
-        self.base_revnum = status.max_rev
+        (min_rev, max_rev, switch, modified) = wc.revision_status(self.basedir, None, True, None)
+        self.base_revnum = max_rev
         self.base_tree = SvnBasisTree(self)
         self.base_revid = branch.generate_revision_id(self.base_revnum)
 
         self.read_working_inventory()
 
-        self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(), 
-                                       'bzr')
+        self.controldir = os.path.join(self.basedir, wc.get_adm_dir(), 'bzr')
         try:
             os.makedirs(self.controldir)
             os.makedirs(os.path.join(self.controldir, 'lock'))
         except OSError:
             pass
         control_transport = bzrdir.transport.clone(urlutils.join(
-                                                   svn.wc.get_adm_dir(), 'bzr'))
+                                                   wc.get_adm_dir(), 'bzr'))
         self._control_files = LockableFiles(control_transport, 'lock', LockDir)
 
     def get_ignore_list(self):
-        ignores = set([svn.wc.get_adm_dir()])
-        ignores.update(svn.wc.get_default_ignores(svn_config))
+        ignore_globs = set([wc.get_adm_dir()])
+        ignore_globs.update(ignores.get_runtime_ignores())
+        ignore_globs.update(ignores.get_user_ignores())
 
-        def dir_add(wc, prefix, patprefix):
-            ignorestr = svn.wc.prop_get(properties.PROP_IGNORE, 
-                                        self.abspath(prefix).rstrip("/"), wc)
+        def dir_add(adm, prefix, patprefix):
+            ignorestr = adm.prop_get(properties.PROP_IGNORE, 
+                                    self.abspath(prefix).rstrip("/"))
             if ignorestr is not None:
                 for pat in ignorestr.splitlines():
-                    ignores.add(urlutils.joinpath(patprefix, pat))
+                    ignore_globs.add(urlutils.joinpath(patprefix, pat))
 
-            entries = svn.wc.entries_read(wc, False)
+            entries = adm.entries_read(False)
             for entry in entries:
                 if entry == "":
                     continue
 
                 # Ignore ignores on things that aren't directories
-                if entries[entry].kind != svn.core.svn_node_dir:
+                if entries[entry].kind != core.NODE_DIR:
                     continue
 
                 subprefix = os.path.join(prefix, entry)
 
-                subwc = svn.wc.adm_open3(wc, self.abspath(subprefix), False, 
-                                         0, None)
+                subwc = wc.WorkingCopy(adm, self.abspath(subprefix))
                 try:
                     dir_add(subwc, subprefix, urlutils.joinpath(patprefix, entry))
                 finally:
-                    svn.wc.adm_close(subwc)
+                    subwc.close()
 
-        wc = self._get_wc()
+        adm = self._get_wc()
         try:
-            dir_add(wc, "", ".")
+            dir_add(adm, "", ".")
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
 
-        return ignores
+        return ignore_globs
 
     def is_control_filename(self, path):
-        return svn.wc.is_adm_dir(path)
+        return wc.is_adm_dir(path)
 
     def apply_inventory_delta(self, changes):
         raise NotImplementedError(self.apply_inventory_delta)
 
-    def update(self, change_reporter=None):
-        rev = svn.core.svn_opt_revision_t()
-        rev.kind = svn.core.svn_opt_revision_head
-        svn.client.update(self.basedir, rev, True, self.client_ctx)
+    def _update(self, revnum=None):
+        if revnum is None:
+            # FIXME: should be able to use -1 here
+            revnum = self.branch.get_revnum()
+        adm = self._get_wc()
+        # FIXME: honor SVN_CONFIG_SECTION_HELPERS:SVN_CONFIG_OPTION_DIFF3_CMD
+        # FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_USE_COMMIT_TIMES
+        # FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_PRESERVED_CF_EXTS
+        try:
+            editor = adm.get_update_editor(self.basedir, use_commit_times=False, recurse=True)
+            assert editor is not None
+            conn = self.branch.repository.transport.get_connection()
+            try:
+                reporter = conn.do_update(revnum, True, editor)
+                adm.crawl_revisions(self.basedir, reporter, restore_files=False, recurse=True, 
+                                    use_commit_times=False)
+            finally:
+                self.branch.repository.transport.add_connection(conn)
+            # FIXME: handle externals
+        finally:
+            adm.close()
+        return revnum
+
+    def update(self, change_reporter=None, possible_transports=None, revnum=None):
+        orig_revnum = self.base_revnum
+        self.base_revnum = self._update(revnum)
+        self.base_revid = self.branch.generate_revision_id(self.base_revnum)
+        self.base_tree = None
+        self.read_working_inventory()
+        return self.base_revnum - orig_revnum
 
     def remove(self, files, verbose=False, to_file=None):
         # FIXME: Use to_file argument
         # FIXME: Use verbose argument
         assert isinstance(files, list)
-        wc = self._get_wc(write_lock=True)
+        adm = self._get_wc(write_lock=True)
         try:
             for file in files:
-                svn.wc.delete2(self.abspath(file), wc, None, None, None)
+                adm.delete(self.abspath(file))
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
 
         for file in files:
             self._change_fileid_mapping(None, file)
         self.read_working_inventory()
 
     def _get_wc(self, relpath="", write_lock=False):
-        return svn.wc.adm_open3(None, self.abspath(relpath).rstrip("/"), 
-                                write_lock, 0, None)
+        return wc.WorkingCopy(None, self.abspath(relpath).rstrip("/"), write_lock)
 
     def _get_rel_wc(self, relpath, write_lock=False):
         dir = os.path.dirname(relpath)
@@ -178,20 +195,18 @@ class SvnWorkingTree(WorkingTree):
     def move(self, from_paths, to_dir=None, after=False, **kwargs):
         # FIXME: Use after argument
         assert after != True
-        revt = svn.core.svn_opt_revision_t()
-        revt.kind = svn.core.svn_opt_revision_working
         for entry in from_paths:
             try:
                 to_wc = self._get_wc(to_dir, write_lock=True)
-                svn.wc.copy(self.abspath(entry), to_wc
+                to_wc.copy(self.abspath(entry)
                             os.path.basename(entry), None, None)
             finally:
-                svn.wc.adm_close(to_wc)
+                to_wc.close()
             try:
                 from_wc = self._get_wc(write_lock=True)
-                svn.wc.delete2(self.abspath(entry), from_wc, None, None, None)
+                from_wc.delete(self.abspath(entry))
             finally:
-                svn.wc.adm_close(from_wc)
+                from_wc.close()
             new_name = urlutils.join(to_dir, os.path.basename(entry))
             self._change_fileid_mapping(self.inventory.path2id(entry), new_name)
             self._change_fileid_mapping(None, entry)
@@ -201,8 +216,6 @@ class SvnWorkingTree(WorkingTree):
     def rename_one(self, from_rel, to_rel, after=False):
         # FIXME: Use after
         assert after != True
-        revt = svn.core.svn_opt_revision_t()
-        revt.kind = svn.core.svn_opt_revision_unspecified
         (to_wc, to_file) = self._get_rel_wc(to_rel, write_lock=True)
         if os.path.dirname(from_rel) == os.path.dirname(to_rel):
             # Prevent lock contention
@@ -211,10 +224,10 @@ class SvnWorkingTree(WorkingTree):
             (from_wc, _) = self._get_rel_wc(from_rel, write_lock=True)
         from_id = self.inventory.path2id(from_rel)
         try:
-            svn.wc.copy(self.abspath(from_rel), to_wc, to_file, None, None)
-            svn.wc.delete2(self.abspath(from_rel), from_wc, None, None, None)
+            to_wc.copy(self.abspath(from_rel), to_file)
+            from_wc.delete(self.abspath(from_rel))
         finally:
-            svn.wc.adm_close(to_wc)
+            to_wc.close()
         self._change_fileid_mapping(None, from_rel)
         self._change_fileid_mapping(from_id, to_rel)
         self.read_working_inventory()
@@ -230,7 +243,7 @@ class SvnWorkingTree(WorkingTree):
         assert isinstance(path, str)
 
         rp = self.branch.unprefix(path)
-        entry = self.base_tree.id_map[rp]
+        entry = self.basis_tree().id_map[rp]
         assert entry[0] is not None
         assert isinstance(entry[0], str), "fileid %r for %r is not a string" % (entry[0], path)
         return entry
@@ -253,7 +266,7 @@ class SvnWorkingTree(WorkingTree):
                 file = InventoryFile(id, os.path.basename(relpath), parent_id)
                 file.revision = revid
                 try:
-                    data = fingerprint_file(open(self.abspath(relpath)))
+                    data = osutils.fingerprint_file(open(self.abspath(relpath)))
                     file.text_sha1 = data['sha1']
                     file.text_size = data['size']
                     file.executable = self.is_executable(id, relpath)
@@ -263,36 +276,36 @@ class SvnWorkingTree(WorkingTree):
                     pass
 
         def find_copies(url, relpath=""):
-            wc = self._get_wc(relpath)
-            entries = svn.wc.entries_read(wc, False)
+            adm = self._get_wc(relpath)
+            entries = adm.entries_read(False)
             for entry in entries.values():
                 subrelpath = os.path.join(relpath, entry.name)
                 if entry.name == "" or entry.kind != 'directory':
                     if ((entry.copyfrom_url == url or entry.url == url) and 
-                        not (entry.schedule in (svn.wc.schedule_delete,
-                                                svn.wc.schedule_replace))):
+                        not (entry.schedule in (wc.SCHEDULE_DELETE,
+                                                wc.SCHEDULE_REPLACE))):
                         yield os.path.join(
                                 self.branch.get_branch_path().strip("/"), 
                                 subrelpath)
                 else:
                     find_copies(subrelpath)
-            svn.wc.adm_close(wc)
+            adm.close()
 
         def find_ids(entry, rootwc):
             relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
-            assert entry.schedule in (svn.wc.schedule_normal
-                                      svn.wc.schedule_delete,
-                                      svn.wc.schedule_add,
-                                      svn.wc.schedule_replace)
-            if entry.schedule == svn.wc.schedule_normal:
+            assert entry.schedule in (wc.SCHEDULE_NORMAL
+                                      wc.SCHEDULE_DELETE,
+                                      wc.SCHEDULE_ADD,
+                                      wc.SCHEDULE_REPLACE)
+            if entry.schedule == wc.SCHEDULE_NORMAL:
                 assert entry.revision >= 0
                 # Keep old id
                 return self.path_to_file_id(entry.cmt_rev, entry.revision, 
                         relpath)
-            elif entry.schedule == svn.wc.schedule_delete:
+            elif entry.schedule == wc.SCHEDULE_DELETE:
                 return (None, None)
-            elif (entry.schedule == svn.wc.schedule_add or 
-                  entry.schedule == svn.wc.schedule_replace):
+            elif (entry.schedule == wc.SCHEDULE_ADD or 
+                  entry.schedule == wc.SCHEDULE_REPLACE):
                 # See if the file this file was copied from disappeared
                 # and has no other copies -> in that case, take id of other file
                 if (entry.copyfrom_url and 
@@ -305,9 +318,9 @@ class SvnWorkingTree(WorkingTree):
                 # FIXME: Generate more random file ids
                 return ("NEW-" + escape_svn_path(entry.url[len(entry.repos):].strip("/")), None)
 
-        def add_dir_to_inv(relpath, wc, parent_id):
+        def add_dir_to_inv(relpath, adm, parent_id):
             assert isinstance(relpath, unicode)
-            entries = svn.wc.entries_read(wc, False)
+            entries = adm.entries_read(False)
             entry = entries[""]
             assert parent_id is None or isinstance(parent_id, str), \
                     "%r is not a string" % parent_id
@@ -332,13 +345,12 @@ class SvnWorkingTree(WorkingTree):
                 entry = entries[name]
                 assert entry
                 
-                if entry.kind == svn.core.svn_node_dir:
-                    subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath), 
-                                             False, 0, None)
+                if entry.kind == core.NODE_DIR:
+                    subwc = wc.WorkingCopy(adm, self.abspath(subrelpath))
                     try:
                         add_dir_to_inv(subrelpath, subwc, id)
                     finally:
-                        svn.wc.adm_close(subwc)
+                        subwc.close()
                 else:
                     (subid, subrevid) = find_ids(entry, rootwc)
                     if subid:
@@ -350,7 +362,7 @@ class SvnWorkingTree(WorkingTree):
         try:
             add_dir_to_inv(u"", rootwc, None)
         finally:
-            svn.wc.adm_close(rootwc)
+            rootwc.close()
 
         self._set_inventory(inv, dirty=False)
         return inv
@@ -360,129 +372,51 @@ class SvnWorkingTree(WorkingTree):
         if revid is None or revid == NULL_REVISION:
             self.base_revid = revid
             self.base_revnum = 0
-            self.base_tree = RevisionTree(self, Inventory(), revid)
+            self.base_tree = None
             return
 
         rev = self.branch.lookup_revision_id(revid)
         self.base_revnum = rev
         self.base_revid = revid
-        self.base_tree = SvnBasisTree(self)
+        self.base_tree = None
 
         # TODO: Implement more efficient version
         newrev = self.branch.repository.get_revision(revid)
         newrevtree = self.branch.repository.revision_tree(revid)
 
-        def update_settings(wc, path):
+        def update_settings(adm, path):
             id = newrevtree.inventory.path2id(path)
             mutter("Updating settings for %r", id)
             revnum = self.branch.lookup_revision_id(
                     newrevtree.inventory[id].revision)
 
-            svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc
+            adm.process_committed(self.abspath(path).rstrip("/")
                           False, revnum, 
-                          svn.core.svn_time_to_cstring(newrev.timestamp), 
-                          newrev.committer, None, False)
+                          time_to_cstring(newrev.timestamp), 
+                          newrev.committer)
 
             if newrevtree.inventory[id].kind != 'directory':
                 return
 
-            entries = svn.wc.entries_read(wc, True)
+            entries = adm.entries_read(True)
             for entry in entries:
                 if entry == "":
                     continue
 
-                subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
+                subwc = wc.WorkingCopy(adm, os.path.join(self.basedir, path, entry), write_lock=True)
                 try:
                     update_settings(subwc, os.path.join(path, entry))
                 finally:
-                    svn.wc.adm_close(subwc)
+                    subwc.close()
 
         # Set proper version for all files in the wc
-        wc = self._get_wc(write_lock=True)
+        adm = self._get_wc(write_lock=True)
         try:
-            update_settings(wc, "")
+            update_settings(adm, "")
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
         self.base_revid = revid
 
-    def commit(self, message=None, message_callback=None, revprops=None, 
-               timestamp=None, timezone=None, committer=None, rev_id=None, 
-               allow_pointless=True, strict=False, verbose=False, local=False, 
-               reporter=None, config=None, specific_files=None, author=None):
-        if author is not None:
-            revprops['author'] = author
-        # FIXME: Use allow_pointless
-        # FIXME: Use verbose
-        # FIXME: Use reporter
-        # FIXME: Use strict
-        if local:
-            raise LocalCommitsUnsupported()
-
-        if specific_files:
-            specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
-        else:
-            specific_files = [self.basedir.encode('utf8')]
-
-        if message_callback is not None:
-            def log_message_func(items, pool):
-                """ Simple log message provider for unit tests. """
-                return message_callback(self).encode("utf-8")
-        else:
-            assert isinstance(message, basestring)
-            def log_message_func(items, pool):
-                """ Simple log message provider for unit tests. """
-                return message.encode("utf-8")
-
-        self.client_ctx.log_msg_baton2 = log_message_func
-        if rev_id is not None:
-            extra = "%d %s\n" % (self.branch.revno()+1, rev_id)
-        else:
-            extra = ""
-        wc = self._get_wc(write_lock=True)
-        try:
-            svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme), 
-                             self._get_bzr_revids(self._get_base_branch_props()) + extra,
-                             self.basedir, wc)
-            svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO, 
-                             generate_revision_metadata(timestamp, 
-                                                        timezone, 
-                                                        committer,
-                                                        revprops),
-                             self.basedir, wc)
-        finally:
-            svn.wc.adm_close(wc)
-
-        try:
-            try:
-                commit_info = svn.client.commit3(specific_files, True, False, 
-                                                 self.client_ctx)
-            except SubversionException, (_, num):
-                if num == ERR_FS_TXN_OUT_OF_DATE:
-                    raise OutOfDateTree(self)
-                raise
-        except:
-            # Reset properties so the next subversion commit won't 
-            # accidently set these properties.
-            wc = self._get_wc(write_lock=True)
-            base_branch_props = self._get_base_branch_props()
-            svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme), 
-                             self._get_bzr_revids(base_branch_props), self.basedir, wc)
-            svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO, 
-                              base_branch_props.get(SVN_PROP_BZR_REVISION_INFO, ""),
-                              self.basedir, wc)
-            svn.wc.adm_close(wc)
-            raise
-
-        self.client_ctx.log_msg_baton2 = None
-
-        revid = self.branch.generate_revision_id(commit_info.revision)
-
-        self.base_revid = revid
-        self.base_revnum = commit_info.revision
-        self.base_tree = SvnBasisTree(self)
-
-        return revid
-
     def smart_add(self, file_list, recurse=True, action=None, save=True):
         assert isinstance(recurse, bool)
         if action is None:
@@ -498,14 +432,14 @@ class SvnWorkingTree(WorkingTree):
             todo = []
             file_path = os.path.abspath(file_path)
             f = self.relpath(file_path)
-            wc = self._get_wc(os.path.dirname(f), write_lock=True)
+            adm = self._get_wc(os.path.dirname(f), write_lock=True)
             try:
                 if not self.inventory.has_filename(f):
                     if save:
                         mutter('adding %r', file_path)
-                        svn.wc.add2(file_path, wc, None, 0, None, None, None)
+                        adm.add(file_path, None, 0, None, None, None)
                     added.append(file_path)
-                if recurse and file_kind(file_path) == 'directory':
+                if recurse and osutils.file_kind(file_path) == 'directory':
                     # Filter out ignored files and update ignored
                     for c in os.listdir(file_path):
                         if self.is_control_filename(c):
@@ -516,7 +450,7 @@ class SvnWorkingTree(WorkingTree):
                             ignored.setdefault(ignore_glob, []).append(c_path)
                         todo.append(c_path)
             finally:
-                svn.wc.adm_close(wc)
+                adm.close()
             if todo != []:
                 cadded, cignored = self.smart_add(todo, recurse, action, save)
                 added.extend(cadded)
@@ -533,13 +467,12 @@ class SvnWorkingTree(WorkingTree):
             ids = iter(ids)
         assert isinstance(files, list)
         for f in files:
-            wc = self._get_wc(os.path.dirname(f), write_lock=True)
+            adm = self._get_wc(os.path.dirname(f), write_lock=True)
             try:
                 try:
-                    svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0, 
-                            None, None, None)
+                    adm.add(os.path.join(self.basedir, f))
                     if ids is not None:
-                        self._change_fileid_mapping(ids.next(), f, wc)
+                        self._change_fileid_mapping(ids.next(), f, adm)
                 except SubversionException, (_, num):
                     if num == ERR_ENTRY_EXISTS:
                         continue
@@ -547,18 +480,22 @@ class SvnWorkingTree(WorkingTree):
                         raise NoSuchFile(path=f)
                     raise
             finally:
-                svn.wc.adm_close(wc)
+                adm.close()
         self.read_working_inventory()
 
     def basis_tree(self):
         if self.base_revid is None or self.base_revid == NULL_REVISION:
             return self.branch.repository.revision_tree(self.base_revid)
 
+        if self.base_tree is None:
+            self.base_tree = SvnBasisTree(self)
+
         return self.base_tree
 
     def pull(self, source, overwrite=False, stop_revision=None, 
              delta_reporter=None, possible_transports=None):
         # FIXME: Use delta_reporter
+        # FIXME: Use source
         # FIXME: Use overwrite
         result = PullResult()
         result.source_branch = source
@@ -567,11 +504,12 @@ class SvnWorkingTree(WorkingTree):
         (result.old_revno, result.old_revid) = self.branch.last_revision_info()
         if stop_revision is None:
             stop_revision = self.branch.last_revision()
-        rev = svn.core.svn_opt_revision_t()
-        rev.kind = svn.core.svn_opt_revision_number
-        rev.value.number = self.branch.lookup_revision_id(stop_revision)
-        fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
+        revnumber = self.branch.lookup_revision_id(stop_revision)
+        fetched = self._update(revnum)
+        self.base_revnum = fetched
         self.base_revid = self.branch.generate_revision_id(fetched)
+        self.base_tree = None
+        self.read_working_inventory()
         result.new_revid = self.base_revid
         result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
         return result
@@ -579,13 +517,13 @@ class SvnWorkingTree(WorkingTree):
     def get_file_sha1(self, file_id, path=None, stat_value=None):
         if not path:
             path = self._inventory.id2path(file_id)
-        return fingerprint_file(open(self.abspath(path)))['sha1']
+        return osutils.fingerprint_file(open(self.abspath(path)))['sha1']
 
-    def _change_fileid_mapping(self, id, path, wc=None):
-        if wc is None:
+    def _change_fileid_mapping(self, id, path, adm=None):
+        if adm is None:
             subwc = self._get_wc(write_lock=True)
         else:
-            subwc = wc
+            subwc = adm 
         new_entries = self._get_new_file_ids(subwc)
         if id is None:
             if new_entries.has_key(path):
@@ -595,17 +533,17 @@ class SvnWorkingTree(WorkingTree):
             new_entries[path] = id
         existing = "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
         if existing != "":
-            svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
-        if wc is None:
-            svn.wc.adm_close(subwc)
+            subwc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir)
+        if adm is None:
+            subwc.close()
 
     def _get_base_branch_props(self):
         return self.branch.repository.branchprop_list.get_properties(
                 self.branch.get_branch_path(self.base_revnum), self.base_revnum)
 
-    def _get_new_file_ids(self, wc):
+    def _get_new_file_ids(self, adm):
         committed = self._get_base_branch_props().get(SVN_PROP_BZR_FILEIDS, "")
-        existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
+        existing = adm.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir)
         if existing is None or committed == existing:
             return {}
         return dict(map(lambda x: str(x).split("\t"), 
@@ -622,7 +560,7 @@ class SvnWorkingTree(WorkingTree):
 
     def set_pending_merges(self, merges):
         """See MutableTree.set_pending_merges()."""
-        wc = self._get_wc(write_lock=True)
+        adm = self._get_wc(write_lock=True)
         try:
             # Set bzr:merge
             if len(merges) > 0:
@@ -630,9 +568,9 @@ class SvnWorkingTree(WorkingTree):
             else:
                 bzr_merge = ""
 
-            svn.wc.prop_set(SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), 
+            adm.prop_set(SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), 
                                  self._get_bzr_merges(self._get_base_branch_props()) + bzr_merge, 
-                                 self.basedir, wc)
+                                 self.basedir)
             
             svk_merges = parse_svk_features(self._get_svk_merges(self._get_base_branch_props()))
 
@@ -643,29 +581,32 @@ class SvnWorkingTree(WorkingTree):
                 except InvalidRevisionId:
                     pass
 
-            svn.wc.prop_set2(SVN_PROP_SVK_MERGE, 
+            adm.prop_set(SVN_PROP_SVK_MERGE, 
                              serialize_svk_features(svk_merges), self.basedir, 
-                             wc, False)
+                             False)
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
 
     def add_pending_merge(self, revid):
         merges = self.pending_merges()
         merges.append(revid)
         self.set_pending_merges(merges)
 
+    def get_parent_ids(self):
+        return [self.base_revid] + self.pending_merges()
+
     def pending_merges(self):
         merged = self._get_bzr_merges(self._get_base_branch_props()).splitlines()
-        wc = self._get_wc()
+        adm = self._get_wc()
         try:
-            merged_data = svn.wc.prop_get(
-                SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), self.basedir, wc)
+            merged_data = adm.prop_get(
+                SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), self.basedir)
             if merged_data is None:
                 set_merged = []
             else:
                 set_merged = merged_data.splitlines()
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
 
         assert (len(merged) == len(set_merged) or 
                len(merged)+1 == len(set_merged))
@@ -675,6 +616,32 @@ class SvnWorkingTree(WorkingTree):
 
         return []
 
+    def path_content_summary(self, path, _lstat=os.lstat,
+        _mapper=osutils.file_kind_from_stat_mode):
+        """See Tree.path_content_summary."""
+        abspath = self.abspath(path)
+        try:
+            stat_result = _lstat(abspath)
+        except OSError, e:
+            if getattr(e, 'errno', None) == errno.ENOENT:
+                # no file.
+                return ('missing', None, None, None)
+            # propagate other errors
+            raise
+        kind = _mapper(stat_result.st_mode)
+        if kind == 'file':
+            size = stat_result.st_size
+            # try for a stat cache lookup
+            executable = self._is_executable_from_path_and_stat(path, stat_result)
+            return (kind, size, executable, self._sha_from_stat(
+                path, stat_result))
+        elif kind == 'directory':
+            return kind, None, None, None
+        elif kind == 'symlink':
+            return ('symlink', None, None, os.readlink(abspath))
+        else:
+            return (kind, None, None, None)
+
     def _reset_data(self):
         pass
 
@@ -688,7 +655,7 @@ class SvnWorkingTree(WorkingTree):
         finally:
             self.branch.unlock()
 
-    if not supports_executable():
+    if not osutils.supports_executable():
         def is_executable(self, file_id, path=None):
             inv = self.basis_tree()._inventory
             if file_id in inv:
@@ -699,16 +666,19 @@ class SvnWorkingTree(WorkingTree):
 
 class SvnWorkingTreeFormat(WorkingTreeFormat):
     """Subversion working copy format."""
+    def __init__(self, version):
+        self.version = version
+
     def __get_matchingbzrdir(self):
         return SvnWorkingTreeDirFormat()
 
     _matchingbzrdir = property(__get_matchingbzrdir)
 
     def get_format_description(self):
-        return "Subversion Working Copy"
+        return "Subversion Working Copy Version %d" % self.version
 
     def get_format_string(self):
-        return "Subversion Working Copy Format"
+        raise NotImplementedError
 
     def initialize(self, a_bzrdir, revision_id=None):
         raise NotImplementedError(self.initialize)
@@ -725,11 +695,14 @@ 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)
+        except SubversionException, (msg, ERR_WC_UNSUPPORTED_FORMAT):
+            raise UnsupportedFormatError(msg, kind='workingtree')
+        try:
+            self.svn_url = adm.entry(self.local_path, True).url
         finally:
-            svn.wc.adm_close(wc)
+            adm.close()
 
         self.remote_transport = SvnRaTransport(self.svn_url)
         self.remote_bzrdir = SvnRemoteAccess(self.remote_transport)