# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-from bzrlib.errors import NoSuchFile, NotBranchError
+from bzrlib.errors import NoSuchFile, NotBranchError, TransportNotPossible
+from bzrlib.trace import mutter
from bzrlib.transport import Transport
+import bzrlib.urlutils as urlutils
from cStringIO import StringIO
import os
+from tempfile import mktemp
+from svn.core import SubversionException, Pool
import svn.ra
+import svn.core
-from scheme import NoBranchingScheme
+# Some older versions of the Python bindings need to be
+# explicitly initialized
+svn.ra.initialize()
-def _create_auth_baton():
- """ Create a Subversion authentication baton. """
+svn_config = svn.core.svn_config_get_config(None)
+
+
+def _create_auth_baton(pool):
+ """Create a Subversion authentication baton. """
import svn.client
# Give the client context baton a suite of authentication
# providers.h
providers = [
- svn.client.svn_client_get_simple_provider(),
- svn.client.svn_client_get_ssl_client_cert_file_provider(),
- svn.client.svn_client_get_ssl_client_cert_pw_file_provider(),
- svn.client.svn_client_get_ssl_server_trust_file_provider(),
- svn.client.svn_client_get_username_provider(),
+ svn.client.get_simple_provider(pool),
+ svn.client.get_username_provider(pool),
+ svn.client.get_ssl_client_cert_file_provider(pool),
+ svn.client.get_ssl_client_cert_pw_file_provider(pool),
+ svn.client.get_ssl_server_trust_file_provider(pool),
]
- return svn.core.svn_auth_open(providers)
+ return svn.core.svn_auth_open(providers, pool)
# Don't run any tests on SvnTransport as it is not intended to be
return []
-class SvnRaCallbacks(svn.ra.callbacks2_t):
- def __init__(self):
- svn.ra.callbacks2_t.__init__(self)
- self.auth_baton = _create_auth_baton()
+def svn_to_bzr_url(url):
+ """Convert a Subversion URL to a URL understood by Bazaar.
- def open_tmp_file(self):
- print "foo"
+ This will optionally prefix the URL with "svn+".
+ """
+ if not (url.startswith("svn+") or url.startswith("svn:")):
+ url = "svn+%s" % url
+ return url
- def progress(self, f, c, pool):
- print "%s: %d / %d" % (self, f, c)
+def bzr_to_svn_url(url):
+ """Convert a Bazaar URL to a URL understood by Subversion.
-class SvnRaTransport(Transport):
- """Fake transport for Subversion-related namespaces. This implements
- just as much of Transport as is necessary to fool Bazaar-NG. """
- def __init__(self, url="", ra=None, root_url=None, scheme=None):
- Transport.__init__(self, url)
-
- if url.startswith("svn://") or \
- url.startswith("svn+ssh://"):
- self.svn_url = url
- else:
- self.svn_url = url[4:] # Skip svn+
-
- # The SVN libraries don't like trailing slashes...
- self.svn_url = self.svn_url.rstrip('/')
-
- callbacks = SvnRaCallbacks()
-
- if not ra:
- self.ra = svn.ra.open2(self.svn_url.encode('utf8'), callbacks, None, None)
- self.svn_root_url = svn.ra.get_repos_root(self.ra)
- if self.svn_root_url != self.svn_url:
- svn.ra.reparent(self.ra, self.svn_root_url.encode('utf8'))
- else:
- self.ra = ra
- self.svn_root_url = root_url
+ This will possibly remove the svn+ prefix.
+ """
+ if (url.startswith("svn+http://") or
+ url.startswith("svn+file://") or
+ url.startswith("svn+https://")):
+ url = url[len("svn+"):] # Skip svn+
- self.root_url = self.svn_root_url
- if not self.root_url.startswith("svn+"):
- self.root_url = "svn+%s" % self.root_url
+ # The SVN libraries don't like trailing slashes...
+ return url.rstrip('/')
- # Browsed above this directory
- if not self.svn_url.startswith(self.svn_root_url):
- raise NotBranchError(url)
- self.path = self.svn_url[len(self.svn_root_url)+1:]
+class SvnRaCallbacks(svn.ra.callbacks2_t):
+ """Remote access callbacks implementation for bzr-svn."""
+ def __init__(self, pool):
+ svn.ra.callbacks2_t.__init__(self)
+ self.auth_baton = _create_auth_baton(pool)
+ self.pool = pool
+
+ def open_tmp_file(self, pool):
+ return mktemp(prefix='bzr-svn')
- if not scheme:
- scheme = NoBranchingScheme()
+class SvnRaTransport(Transport):
+ """Fake transport for Subversion-related namespaces.
+
+ This implements just as much of Transport as is necessary
+ to fool Bazaar-NG. """
+ def __init__(self, url="", ra=None):
+ self.pool = Pool()
+ bzr_url = url
+ self.svn_url = bzr_to_svn_url(url)
+ Transport.__init__(self, bzr_url)
+
+ # Only Subversion 1.4 has reparent()
+ if ra is None or not hasattr(svn.ra, 'reparent'):
+ self.callbacks = SvnRaCallbacks(self.pool)
+ try:
+ mutter('opening SVN RA connection to %r' % self.svn_url)
+ self.ra = svn.ra.open2(self.svn_url.encode('utf8'), self.callbacks, svn_config, None)
+ except SubversionException, (_, num):
+ if num == svn.core.SVN_ERR_RA_ILLEGAL_URL:
+ raise NotBranchError(path=url)
+ if num == svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED:
+ raise NotBranchError(path=url)
+ if num == svn.core.SVN_ERR_BAD_URL:
+ raise NotBranchError(path=url)
+ raise
- self._scheme = scheme
- self.is_branch_root = scheme.is_branch(self.path)
+ else:
+ self.ra = ra
+ mutter('svn reparent %r' % self.svn_url)
+ svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
def has(self, relpath):
+ """See Transport.has()."""
+ # TODO: Raise TransportNotPossible here instead and
+ # catch it in bzrdir.py
return False
def get(self, relpath):
- raise NoSuchFile(relpath)
+ """See Transport.get()."""
+ # TODO: Raise TransportNotPossible here instead and
+ # catch it in bzrdir.py
+ raise NoSuchFile(path=relpath)
def stat(self, relpath):
- return os.stat('.') #FIXME
+ """See Transport.stat()."""
+ raise TransportNotPossible('stat not supported on Subversion')
+
+ def get_root(self):
+ """Open a connection to the root of this repository.
+
+ :return: A new instance of SvnRaTransport connected to the root.
+ """
+ return SvnRaTransport(svn_to_bzr_url(svn.ra.get_repos_root(self.ra)), ra=self.ra)
def listable(self):
+ """See Transport.listable().
+
+ :return: False.
+ """
return False
+ # There is no real way to do locking directly on the transport
+ # nor is there a need to.
+ class PhonyLock:
+ def unlock(self):
+ pass
+
+ def lock_write(self, relpath):
+ """See Transport.lock_write()."""
+ return self.PhonyLock()
+
def lock_read(self, relpath):
- class PhonyLock:
- def unlock(self):
- pass
- return PhonyLock()
+ """See Transport.lock_read()."""
+ return self.PhonyLock()
- def clone(self, path):
- parts = self.svn_url.split("/")
-
- # FIXME: Handle more complicated paths
- if path == '..':
- parts.pop()
- elif path != '.':
- parts.append(path)
+ def clone(self, offset=None):
+ """See Transport.clone()."""
+ if offset is None:
+ return self.__class__(self.base)
- return SvnRaTransport("/".join(parts), ra=self.ra, root_url=self.svn_root_url)
+ return SvnRaTransport(urlutils.join(self.base, offset), ra=self.ra)