/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

Allow Repository tests to be backed onto a specific VFS as needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
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
# TODO: At some point, handle upgrades by just passing the whole request
 
18
# across to run on the server.
 
19
 
 
20
from cStringIO import StringIO
 
21
from urlparse import urlparse
 
22
 
 
23
from bzrlib import branch, errors, repository
 
24
from bzrlib.branch import BranchReferenceFormat
 
25
from bzrlib.bzrdir import BzrDir, BzrDirFormat, RemoteBzrDirFormat
 
26
from bzrlib.revision import NULL_REVISION
 
27
from bzrlib.smart import client, vfs
 
28
from bzrlib.urlutils import unescape
 
29
 
 
30
# Note: RemoteBzrDirFormat is in bzrdir.py
 
31
 
 
32
class RemoteBzrDir(BzrDir):
 
33
    """Control directory on a remote server, accessed by HPSS."""
 
34
 
 
35
    def __init__(self, transport, _client=None):
 
36
        """Construct a RemoteBzrDir.
 
37
 
 
38
        :param _client: Private parameter for testing. Disables probing and the
 
39
            use of a real bzrdir.
 
40
        """
 
41
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
42
        if _client is not None:
 
43
            self.client = _client
 
44
            return
 
45
 
 
46
        self.client = transport.get_smart_client()
 
47
        # this object holds a delegated bzrdir that uses file-level operations
 
48
        # to talk to the other side
 
49
        # XXX: We should go into find_format, but not allow it to find
 
50
        # RemoteBzrDirFormat and make sure it finds the real underlying format.
 
51
        
 
52
        # THIS IS A COMPLETE AND UTTER LIE.
 
53
        # XXX: XXX: XXX: must be removed before merging to mainline
 
54
        # SMART_SERVER_MERGE_BLOCKER
 
55
        default_format = BzrDirFormat.get_default_format()
 
56
        self._real_bzrdir = default_format.open(transport, _found=True)
 
57
        smartclient = client.SmartClient(self.client)
 
58
        path = self._path_for_remote_call(smartclient)
 
59
        #self._real_bzrdir._format.probe_transport(transport)
 
60
        response = smartclient.call('probe_dont_use', path)
 
61
        if response == ('no',):
 
62
            raise errors.NotBranchError(path=transport.base)
 
63
 
 
64
    def create_repository(self, shared=False):
 
65
        return RemoteRepository(
 
66
            self, self._real_bzrdir.create_repository(shared=shared))
 
67
 
 
68
    def create_branch(self):
 
69
        real_branch = self._real_bzrdir.create_branch()
 
70
        real_repository = real_branch.repository
 
71
        remote_repository = RemoteRepository(self, real_repository)
 
72
        return RemoteBranch(self, remote_repository, real_branch)
 
73
 
 
74
    def create_workingtree(self, revision_id=None):
 
75
        real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
 
76
        return RemoteWorkingTree(self, real_workingtree)
 
77
 
 
78
    def open_branch(self, _unsupported=False):
 
79
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
80
        smartclient = client.SmartClient(self.client)
 
81
        path = self._path_for_remote_call(smartclient)
 
82
        response = smartclient.call('BzrDir.open_branch', path)
 
83
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
84
        if response[0] != 'ok':
 
85
            # this should probably be a regular translate no ?
 
86
            raise errors.NotBranchError(path=self.root_transport.base)
 
87
        if response[1] == '':
 
88
            # branch at this location.
 
89
            if vfs.vfs_enabled():
 
90
                # if the VFS is enabled, create a local object using the VFS.
 
91
                real_branch = self._real_bzrdir.open_branch(unsupported=_unsupported)
 
92
                # This branch accessed through the smart server, so wrap the
 
93
                # file-level objects.
 
94
                real_repository = real_branch.repository
 
95
                assert isinstance(real_repository.bzrdir, RemoteBzrDir)
 
96
                remote_repository = RemoteRepository(real_repository.bzrdir, real_repository)
 
97
                return RemoteBranch(self, remote_repository, real_branch)
 
98
            else:
 
99
                # otherwise just create a proxy for the branch.
 
100
                return RemoteBranch(self, self.find_repository())
 
101
        else:
 
102
            # a branch reference, use the existing BranchReference logic.
 
103
            format = BranchReferenceFormat()
 
104
            return format.open(self, _found=True, location=response[1])
 
105
 
 
106
    def open_repository(self):
 
107
        smartclient = client.SmartClient(self.client)
 
108
        path = self._path_for_remote_call(smartclient)
 
109
        response = smartclient.call('BzrDir.find_repository', path)
 
110
        assert response[0] in ('ok', 'norepository'), \
 
111
            'unexpected response code %s' % (response,)
 
112
        if response[0] == 'norepository':
 
113
            raise errors.NoRepositoryPresent(self)
 
114
        if response[1] == '':
 
115
            if vfs.vfs_enabled():
 
116
                return RemoteRepository(self, self._real_bzrdir.open_repository())
 
117
            else:
 
118
                return RemoteRepository(self)
 
119
        else:
 
120
            raise errors.NoRepositoryPresent(self)
 
121
 
 
122
    def open_workingtree(self):
 
123
        return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
 
124
 
 
125
    def _path_for_remote_call(self, client):
 
126
        """Return the path to be used for this bzrdir in a remote call."""
 
127
        return client.remote_path_from_transport(self.root_transport)
 
128
 
 
129
    def get_branch_transport(self, branch_format):
 
130
        return self._real_bzrdir.get_branch_transport(branch_format)
 
131
 
 
132
    def get_repository_transport(self, repository_format):
 
133
        return self._real_bzrdir.get_repository_transport(repository_format)
 
134
 
 
135
    def get_workingtree_transport(self, workingtree_format):
 
136
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
137
 
 
138
    def can_convert_format(self):
 
139
        """Upgrading of remote bzrdirs is not supported yet."""
 
140
        return False
 
141
 
 
142
    def needs_format_conversion(self, format=None):
 
143
        """Upgrading of remote bzrdirs is not supported yet."""
 
144
        return False
 
145
 
 
146
 
 
147
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
148
    """Format for repositories accessed over rpc.
 
149
 
 
150
    Instances of this repository are represented by RemoteRepository
 
151
    instances.
 
152
    """
 
153
 
 
154
    _matchingbzrdir = RemoteBzrDirFormat
 
155
 
 
156
    def initialize(self, a_bzrdir, shared=False):
 
157
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
158
        return a_bzrdir.create_repository(shared=shared)
 
159
    
 
160
    def open(self, a_bzrdir):
 
161
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
162
        return a_bzrdir.open_repository()
 
163
 
 
164
    def get_format_description(self):
 
165
        return 'bzr remote repository'
 
166
 
 
167
    def __eq__(self, other):
 
168
        return self.__class__ == other.__class__
 
169
 
 
170
    rich_root_data = False
 
171
 
 
172
 
 
173
class RemoteRepository(object):
 
174
    """Repository accessed over rpc.
 
175
 
 
176
    For the moment everything is delegated to IO-like operations over
 
177
    the transport.
 
178
    """
 
179
 
 
180
    def __init__(self, remote_bzrdir, real_repository=None, _client=None):
 
181
        """Create a RemoteRepository instance.
 
182
        
 
183
        :param remote_bzrdir: The bzrdir hosting this repository.
 
184
        :param real_repository: If not None, a local implementation of the
 
185
            repository logic for the repository, usually accessing the data
 
186
            via the VFS.
 
187
        :param _client: Private testing parameter - override the smart client
 
188
            to be used by the repository.
 
189
        """
 
190
        if real_repository:
 
191
            self._real_repository = real_repository
 
192
        self.bzrdir = remote_bzrdir
 
193
        if _client is None:
 
194
            self._client = client.SmartClient(self.bzrdir.client)
 
195
        else:
 
196
            self._client = _client
 
197
        self._format = RemoteRepositoryFormat()
 
198
 
 
199
    def has_revision(self, revision_id):
 
200
        """See Repository.has_revision()."""
 
201
        path = self.bzrdir._path_for_remote_call(self._client)
 
202
        response = self._client.call('Repository.has_revision', path, revision_id.encode('utf8'))
 
203
        assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
 
204
        return response[0] == 'ok'
 
205
 
 
206
    def gather_stats(self, revid, committers=None):
 
207
        """See Repository.gather_stats()."""
 
208
        # SMART_SERVER_MERGE_BLOCKER
 
209
        return self._real_repository.gather_stats(revid, committers)
 
210
 
 
211
    def get_physical_lock_status(self):
 
212
        """See Repository.get_physical_lock_status()."""
 
213
        return False
 
214
 
 
215
    def is_shared(self):
 
216
        """See Repository.is_shared()."""
 
217
        path = self.bzrdir._path_for_remote_call(self._client)
 
218
        response = self._client.call('Repository.is_shared', path)
 
219
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
220
        return response[0] == 'yes'
 
221
 
 
222
    def lock_read(self):
 
223
        # wrong eventually - want a local lock cache context
 
224
        return self._real_repository.lock_read()
 
225
 
 
226
    def lock_write(self):
 
227
        # definately wrong: want to check if there is a real repo
 
228
        # and not thunk through if not
 
229
        return self._real_repository.lock_write()
 
230
 
 
231
    def unlock(self):
 
232
        # should free cache context.
 
233
        return self._real_repository.unlock()
 
234
 
 
235
    def break_lock(self):
 
236
        # should hand off to the network - or better yet, we should not
 
237
        # allow stale network locks ?
 
238
        return self._real_repository.break_lock()
 
239
 
 
240
 
 
241
class RemoteBranchLockableFiles(object):
 
242
    """A 'LockableFiles' implementation that talks to a smart server.
 
243
    
 
244
    This is not a public interface class.
 
245
    """
 
246
 
 
247
    def __init__(self, bzrdir, _client):
 
248
        self.bzrdir = bzrdir
 
249
        self._client = _client
 
250
 
 
251
    def get(self, path):
 
252
        """'get' a remote path as per the LockableFiles interface.
 
253
 
 
254
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
255
             just retrieve a file, instead we ask the smart server to generate
 
256
             a configuration for us - which is retrieved as an INI file.
 
257
        """
 
258
        assert path == 'branch.conf'
 
259
        path = self.bzrdir._path_for_remote_call(self._client)
 
260
        response = self._client.call2('Branch.get_config_file', path)
 
261
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
262
        return StringIO(response[1].read_body_bytes())
 
263
 
 
264
 
 
265
class RemoteBranchFormat(branch.BranchFormat):
 
266
 
 
267
    def get_format_description(self):
 
268
        return 'Remote BZR Branch'
 
269
 
 
270
    def open(self, a_bzrdir):
 
271
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
272
        return a_bzrdir.open_branch()
 
273
 
 
274
    def initialize(self, a_bzrdir):
 
275
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
276
        return a_bzrdir.create_branch()
 
277
 
 
278
 
 
279
class RemoteBranch(branch.Branch):
 
280
    """Branch stored on a server accessed by HPSS RPC.
 
281
 
 
282
    At the moment most operations are mapped down to simple file operations.
 
283
    """
 
284
 
 
285
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
286
        _client=None):
 
287
        """Create a RemoteBranch instance.
 
288
 
 
289
        :param real_branch: An optional local implementation of the branch
 
290
            format, usually accessing the data via the VFS.
 
291
        :param _client: Private parameter for testing.
 
292
        """
 
293
        self.bzrdir = remote_bzrdir
 
294
        if _client is not None:
 
295
            self._client = _client
 
296
        else:
 
297
            self._client = client.SmartClient(self.bzrdir.client)
 
298
        self.repository = remote_repository
 
299
        if real_branch is not None:
 
300
            self._real_branch = real_branch
 
301
        # Fill out expected attributes of branch for bzrlib api users.
 
302
        self._format = RemoteBranchFormat()
 
303
        self.base = self.bzrdir.root_transport.base
 
304
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
305
 
 
306
    def get_physical_lock_status(self):
 
307
        """See Branch.get_physical_lock_status()."""
 
308
        # should be an API call to the server, as branches must be lockable.
 
309
        return self._real_branch.get_physical_lock_status()
 
310
 
 
311
    def lock_read(self):
 
312
        return self._real_branch.lock_read()
 
313
 
 
314
    def lock_write(self):
 
315
        return self._real_branch.lock_write()
 
316
 
 
317
    def unlock(self):
 
318
        return self._real_branch.unlock()
 
319
 
 
320
    def break_lock(self):
 
321
        return self._real_branch.break_lock()
 
322
 
 
323
    def last_revision_info(self):
 
324
        """See Branch.last_revision_info()."""
 
325
        path = self.bzrdir._path_for_remote_call(self._client)
 
326
        response = self._client.call('Branch.last_revision_info', path)
 
327
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
328
        revno = int(response[1])
 
329
        last_revision = response[2].decode('utf8')
 
330
        if last_revision == '':
 
331
            last_revision = NULL_REVISION
 
332
        return (revno, last_revision)
 
333
 
 
334
    def revision_history(self):
 
335
        """See Branch.revision_history()."""
 
336
        # XXX: TODO: this does not cache the revision history for the duration
 
337
        # of a lock, which is a bug - see the code for regular branches
 
338
        # for details.
 
339
        path = self.bzrdir._path_for_remote_call(self._client)
 
340
        response = self._client.call2('Branch.revision_history', path)
 
341
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
342
        result = response[1].read_body_bytes().decode('utf8').split('\x00')
 
343
        if result == ['']:
 
344
            return []
 
345
        return result
 
346
 
 
347
    def set_revision_history(self, rev_history):
 
348
        return self._real_branch.set_revision_history(rev_history)
 
349
 
 
350
    def get_parent(self):
 
351
        return self._real_branch.get_parent()
 
352
        
 
353
    def set_parent(self, url):
 
354
        return self._real_branch.set_parent(url)
 
355
        
 
356
 
 
357
class RemoteWorkingTree(object):
 
358
 
 
359
    def __init__(self, remote_bzrdir, real_workingtree):
 
360
        self.real_workingtree = real_workingtree
 
361
        self.bzrdir = remote_bzrdir
 
362
 
 
363
    def __getattr__(self, name):
 
364
        # XXX: temporary way to lazily delegate everything to the real
 
365
        # workingtree
 
366
        return getattr(self.real_workingtree, name)
 
367
 
 
368