Bunch of file id improvements.
[jelmer/subvertpy.git] / transport.py
1 # Copyright (C) 2006 Jelmer Vernooij <jelmer@samba.org>
2
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.
7
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.
12
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
16
17 from bzrlib.errors import NoSuchFile, NotBranchError, TransportNotPossible
18 from bzrlib.trace import mutter
19 from bzrlib.transport import Transport
20 import bzrlib.urlutils as urlutils
21
22 from cStringIO import StringIO
23 import os
24 from tempfile import mktemp
25
26 from svn.core import SubversionException, Pool
27 import svn.ra
28 import svn.core
29
30 # Some older versions of the Python bindings need to be 
31 # explicitly initialized
32 svn.ra.initialize()
33
34 svn_config = svn.core.svn_config_get_config(None)
35
36
37 def _create_auth_baton(pool):
38     """Create a Subversion authentication baton. """
39     import svn.client
40     # Give the client context baton a suite of authentication
41     # providers.h
42     providers = [
43         svn.client.get_simple_provider(pool),
44         svn.client.get_username_provider(pool),
45         svn.client.get_ssl_client_cert_file_provider(pool),
46         svn.client.get_ssl_client_cert_pw_file_provider(pool),
47         svn.client.get_ssl_server_trust_file_provider(pool),
48         ]
49     return svn.core.svn_auth_open(providers, pool)
50
51
52 # Don't run any tests on SvnTransport as it is not intended to be 
53 # a full implementation of Transport
54 def get_test_permutations():
55     return []
56
57
58 def svn_to_bzr_url(url):
59     """Convert a Subversion URL to a URL understood by Bazaar.
60
61     This will optionally prefix the URL with "svn+".
62     """
63     if not (url.startswith("svn+") or url.startswith("svn:")):
64         url = "svn+%s" % url
65     return url
66
67
68 def bzr_to_svn_url(url):
69     """Convert a Bazaar URL to a URL understood by Subversion.
70
71     This will possibly remove the svn+ prefix.
72     """
73     if (url.startswith("svn+http://") or 
74         url.startswith("svn+file://") or
75         url.startswith("svn+https://")):
76         url = url[len("svn+"):] # Skip svn+
77
78     # The SVN libraries don't like trailing slashes...
79     return url.rstrip('/')
80
81
82 class SvnRaCallbacks(svn.ra.callbacks2_t):
83     """Remote access callbacks implementation for bzr-svn."""
84     def __init__(self, pool):
85         svn.ra.callbacks2_t.__init__(self)
86         self.auth_baton = _create_auth_baton(pool)
87         self.pool = pool
88     
89     def open_tmp_file(self, pool):
90         return mktemp(prefix='bzr-svn')
91
92 class SvnRaTransport(Transport):
93     """Fake transport for Subversion-related namespaces.
94     
95     This implements just as much of Transport as is necessary 
96     to fool Bazaar-NG. """
97     def __init__(self, url="", ra=None):
98         self.pool = Pool()
99         bzr_url = url
100         self.svn_url = bzr_to_svn_url(url)
101         Transport.__init__(self, bzr_url)
102
103                 # Only Subversion 1.4 has reparent()
104         if ra is None or not hasattr(svn.ra, 'reparent'):
105             self.callbacks = SvnRaCallbacks(self.pool)
106             try:
107                 mutter('opening SVN RA connection to %r' % self.svn_url)
108                 self.ra = svn.ra.open2(self.svn_url.encode('utf8'), self.callbacks, svn_config, None)
109             except SubversionException, (_, num):
110                 if num == svn.core.SVN_ERR_RA_ILLEGAL_URL:
111                     raise NotBranchError(path=url)
112                 if num == svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED:
113                     raise NotBranchError(path=url)
114                 if num == svn.core.SVN_ERR_BAD_URL:
115                     raise NotBranchError(path=url)
116                 raise
117
118         else:
119             self.ra = ra
120             mutter('svn reparent %r' % self.svn_url)
121             svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
122
123     def has(self, relpath):
124         """See Transport.has()."""
125         # TODO: Raise TransportNotPossible here instead and 
126         # catch it in bzrdir.py
127         return False
128
129     def get(self, relpath):
130         """See Transport.get()."""
131         # TODO: Raise TransportNotPossible here instead and 
132         # catch it in bzrdir.py
133         raise NoSuchFile(path=relpath)
134
135     def stat(self, relpath):
136         """See Transport.stat()."""
137         raise TransportNotPossible('stat not supported on Subversion')
138
139     def get_root(self):
140         """Open a connection to the root of this repository.
141         
142         :return: A new instance of SvnRaTransport connected to the root.
143         """
144         return SvnRaTransport(svn_to_bzr_url(svn.ra.get_repos_root(self.ra)), ra=self.ra)
145
146     def listable(self):
147         """See Transport.listable().
148         
149         :return: False.
150         """
151         return False
152
153     # There is no real way to do locking directly on the transport 
154     # nor is there a need to.
155     class PhonyLock:
156         def unlock(self):
157             pass
158
159     def lock_write(self, relpath):
160         """See Transport.lock_write()."""
161         return self.PhonyLock()
162
163     def lock_read(self, relpath):
164         """See Transport.lock_read()."""
165         return self.PhonyLock()
166
167     def clone(self, offset=None):
168         """See Transport.clone()."""
169         if offset is None:
170             return self.__class__(self.base)
171
172         return SvnRaTransport(urlutils.join(self.base, offset), ra=self.ra)