/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

  • Committer: Andrew Bennetts
  • Date: 2007-02-05 05:16:09 UTC
  • mto: (2018.5.72 hpss)
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070205051609-1oxwbeg70yzruqjt
Fix test_wsgi.TestWSGI.test_chrooting to work with the new chroot semantics.

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