1 # Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 from bzrlib.errors import (NoSuchFile, NotBranchError, TransportNotPossible,
19 from bzrlib.trace import mutter
20 from bzrlib.transport import Transport
21 import bzrlib.urlutils as urlutils
23 from cStringIO import StringIO
25 from tempfile import mktemp
27 from svn.core import SubversionException, Pool
32 # Some older versions of the Python bindings need to be
33 # explicitly initialized
36 svn_config = svn.core.svn_config_get_config(None)
39 def need_lock(unbound):
40 def locked(self, *args, **kwargs):
43 return unbound(self, *args, **kwargs)
46 locked.__doc__ = unbound.__doc__
47 locked.__name__ = unbound.__name__
51 def _create_auth_baton(pool):
52 """Create a Subversion authentication baton. """
53 # Give the client context baton a suite of authentication
56 svn.client.get_simple_provider(pool),
57 svn.client.get_username_provider(pool),
58 svn.client.get_ssl_client_cert_file_provider(pool),
59 svn.client.get_ssl_client_cert_pw_file_provider(pool),
60 svn.client.get_ssl_server_trust_file_provider(pool),
62 return svn.core.svn_auth_open(providers, pool)
65 # Don't run any tests on SvnTransport as it is not intended to be
66 # a full implementation of Transport
67 def get_test_permutations():
71 def get_svn_ra_transport(bzr_transport):
72 """Obtain corresponding SvnRaTransport for a stock Bazaar transport."""
73 if isinstance(bzr_transport, SvnRaTransport):
76 return SvnRaTransport(bzr_transport.base)
79 def bzr_to_svn_url(url):
80 """Convert a Bazaar URL to a URL understood by Subversion.
82 This will possibly remove the svn+ prefix.
84 if (url.startswith("svn+http://") or
85 url.startswith("svn+file://") or
86 url.startswith("svn+https://")):
87 url = url[len("svn+"):] # Skip svn+
89 # The SVN libraries don't like trailing slashes...
90 return url.rstrip('/')
93 class SvnRaTransport(Transport):
94 """Fake transport for Subversion-related namespaces.
96 This implements just as much of Transport as is necessary
97 to fool Bazaar-NG. """
98 def __init__(self, url=""):
100 self.is_locked = False
102 self.svn_url = bzr_to_svn_url(url)
103 Transport.__init__(self, bzr_url)
105 self._client = svn.client.create_context(self.pool)
106 self._client.auth_baton = _create_auth_baton(self.pool)
109 mutter('opening SVN RA connection to %r' % self.svn_url)
110 self._ra = svn.client.open_ra_session(self.svn_url.encode('utf8'),
111 self._client, self.pool)
112 except SubversionException, (msg, num):
113 if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL, \
114 svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, \
115 svn.core.SVN_ERR_BAD_URL):
116 raise NotBranchError(path=url)
120 assert (not self.is_locked)
121 self.is_locked = True
124 assert self.is_locked
125 self.is_locked = False
127 def has(self, relpath):
128 """See Transport.has()."""
129 # TODO: Raise TransportNotPossible here instead and
130 # catch it in bzrdir.py
133 def get(self, relpath):
134 """See Transport.get()."""
135 # TODO: Raise TransportNotPossible here instead and
136 # catch it in bzrdir.py
137 raise NoSuchFile(path=relpath)
139 def stat(self, relpath):
140 """See Transport.stat()."""
141 raise TransportNotPossible('stat not supported on Subversion')
145 mutter('svn get-uuid')
146 return svn.ra.get_uuid(self._ra)
149 def get_repos_root(self):
150 mutter("svn get-repos-root")
151 return svn.ra.get_repos_root(self._ra)
154 def get_latest_revnum(self):
155 mutter("svn get-latest-revnum")
156 return svn.ra.get_latest_revnum(self._ra)
159 def do_switch(self, switch_rev, switch_target, recurse, switch_url, *args, **kwargs):
160 mutter('svn switch -r %d %r -> %r' % (switch_rev, switch_target, switch_url))
161 return svn.ra.do_switch(self._ra, switch_rev, switch_target, recurse, switch_url, *args, **kwargs)
164 def get_log(self, path, from_revnum, to_revnum, *args, **kwargs):
165 mutter('svn log %r:%r %r' % (from_revnum, to_revnum, path))
166 return svn.ra.get_log(self._ra, [path], from_revnum, to_revnum, *args, **kwargs)
169 def reparent(self, url):
170 url = url.rstrip("/")
171 if url == self.svn_url:
175 if hasattr(svn.ra, 'reparent'):
176 mutter('svn reparent %r' % url)
177 svn.ra.reparent(self._ra, url, self.pool)
179 self._ra = svn.client.open_ra_session(self.svn_url.encode('utf8'),
180 self._client, self.pool)
182 def get_dir(self, path, revnum, pool=None, kind=False):
183 mutter("svn ls -r %d '%r'" % (revnum, path))
184 path = path.rstrip("/")
185 # ra_dav backends fail with strange errors if the path starts with a
186 # slash while other backends don't.
187 assert len(path) == 0 or path[0] != "/"
188 if hasattr(svn.ra, 'get_dir2'):
191 fields += svn.core.SVN_DIRENT_KIND
192 return svn.ra.get_dir2(self._ra, path, revnum, fields)
194 return svn.ra.get_dir(self._ra, path, revnum)
196 def list_dir(self, relpath):
197 assert len(relpath) == 0 or relpath[0] != "/"
201 (dirents, _, _) = self.get_dir(relpath.rstrip("/"),
202 self.get_latest_revnum())
203 except SubversionException, (msg, num):
204 if num == svn.core.SVN_ERR_FS_NOT_DIRECTORY:
205 raise NoSuchFile(relpath)
207 return dirents.keys()
210 def check_path(self, path, revnum, *args, **kwargs):
211 assert len(path) == 0 or path[0] != "/"
212 mutter("svn check_path -r%d %s" % (revnum, path))
213 return svn.ra.check_path(self._ra, path, revnum, *args, **kwargs)
216 def mkdir(self, relpath, mode=None):
217 path = "%s/%s" % (self.svn_url, relpath)
219 svn.client.mkdir([path.encode("utf-8")], self._client)
220 except SubversionException, (msg, num):
221 if num == svn.core.SVN_ERR_FS_NOT_FOUND:
222 raise NoSuchFile(path)
223 if num == svn.core.SVN_ERR_FS_ALREADY_EXISTS:
224 raise FileExists(path)
228 def do_update(self, revnum, path, *args, **kwargs):
229 mutter('svn update -r %r %r' % (revnum, path))
230 return svn.ra.do_update(self._ra, revnum, path, *args, **kwargs)
233 def get_commit_editor(self, *args, **kwargs):
234 return svn.ra.get_commit_editor(self._ra, *args, **kwargs)
237 """See Transport.listable().
241 # There is no real way to do locking directly on the transport
242 # nor is there a need to as the remote server will take care of
248 def lock_write(self, relpath):
249 """See Transport.lock_write()."""
250 return self.PhonyLock()
252 def lock_read(self, relpath):
253 """See Transport.lock_read()."""
254 return self.PhonyLock()
256 def clone(self, offset=None):
257 """See Transport.clone()."""
259 return SvnRaTransport(self.base)
261 return SvnRaTransport(urlutils.join(self.base, offset))