/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

Add a Transport.is_readonly remote call, let {Branch,Repository}.lock_write remote call return UnlockableTransport, and miscellaneous test fixes.

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.config import BranchConfig, TreeConfig
 
27
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
28
from bzrlib.errors import NoSuchRevision
 
29
from bzrlib.revision import NULL_REVISION
 
30
from bzrlib.smart import client, vfs
 
31
from bzrlib.urlutils import unescape
 
32
 
 
33
# Note: RemoteBzrDirFormat is in bzrdir.py
 
34
 
 
35
class RemoteBzrDir(BzrDir):
 
36
    """Control directory on a remote server, accessed by HPSS."""
 
37
 
 
38
    def __init__(self, transport, _client=None):
 
39
        """Construct a RemoteBzrDir.
 
40
 
 
41
        :param _client: Private parameter for testing. Disables probing and the
 
42
            use of a real bzrdir.
 
43
        """
 
44
        BzrDir.__init__(self, transport, RemoteBzrDirFormat())
 
45
        # this object holds a delegated bzrdir that uses file-level operations
 
46
        # to talk to the other side
 
47
        # XXX: We should go into find_format, but not allow it to find
 
48
        # RemoteBzrDirFormat and make sure it finds the real underlying format.
 
49
        self._real_bzrdir = None
 
50
 
 
51
        if _client is None:
 
52
            self._medium = transport.get_smart_client()
 
53
            self._client = client.SmartClient(self._medium)
 
54
        else:
 
55
            self._client = _client
 
56
            self._medium = None
 
57
            return
 
58
 
 
59
        self._ensure_real()
 
60
        path = self._path_for_remote_call(self._client)
 
61
        #self._real_bzrdir._format.probe_transport(transport)
 
62
        response = self._client.call('probe_dont_use', path)
 
63
        if response == ('no',):
 
64
            raise errors.NotBranchError(path=transport.base)
 
65
 
 
66
    def _ensure_real(self):
 
67
        """Ensure that there is a _real_bzrdir set.
 
68
 
 
69
        used before calls to self._real_bzrdir.
 
70
        """
 
71
        if not self._real_bzrdir:
 
72
            default_format = BzrDirFormat.get_default_format()
 
73
            self._real_bzrdir = default_format.open(self.root_transport,
 
74
                _found=True)
 
75
 
 
76
    def create_repository(self, shared=False):
 
77
        return RemoteRepository(
 
78
            self, self._real_bzrdir.create_repository(shared=shared))
 
79
 
 
80
    def create_branch(self):
 
81
        real_branch = self._real_bzrdir.create_branch()
 
82
        return RemoteBranch(self, self.find_repository(), real_branch)
 
83
 
 
84
    def create_workingtree(self, revision_id=None):
 
85
        real_workingtree = self._real_bzrdir.create_workingtree(revision_id=revision_id)
 
86
        return RemoteWorkingTree(self, real_workingtree)
 
87
 
 
88
    def open_branch(self, _unsupported=False):
 
89
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
90
        path = self._path_for_remote_call(self._client)
 
91
        response = self._client.call('BzrDir.open_branch', path)
 
92
        if response[0] == 'ok':
 
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
        elif response == ('nobranch',):
 
101
            raise errors.NotBranchError(path=self.root_transport.base)
 
102
        else:
 
103
            assert False, 'unexpected response code %r' % (response,)
 
104
                
 
105
    def open_repository(self):
 
106
        path = self._path_for_remote_call(self._client)
 
107
        response = self._client.call('BzrDir.find_repository', path)
 
108
        assert response[0] in ('ok', 'norepository'), \
 
109
            'unexpected response code %s' % (response,)
 
110
        if response[0] == 'norepository':
 
111
            raise errors.NoRepositoryPresent(self)
 
112
        if response[1] == '':
 
113
            return RemoteRepository(self)
 
114
        else:
 
115
            raise errors.NoRepositoryPresent(self)
 
116
 
 
117
    def open_workingtree(self):
 
118
        return RemoteWorkingTree(self, self._real_bzrdir.open_workingtree())
 
119
 
 
120
    def _path_for_remote_call(self, client):
 
121
        """Return the path to be used for this bzrdir in a remote call."""
 
122
        return client.remote_path_from_transport(self.root_transport)
 
123
 
 
124
    def get_branch_transport(self, branch_format):
 
125
        return self._real_bzrdir.get_branch_transport(branch_format)
 
126
 
 
127
    def get_repository_transport(self, repository_format):
 
128
        return self._real_bzrdir.get_repository_transport(repository_format)
 
129
 
 
130
    def get_workingtree_transport(self, workingtree_format):
 
131
        return self._real_bzrdir.get_workingtree_transport(workingtree_format)
 
132
 
 
133
    def can_convert_format(self):
 
134
        """Upgrading of remote bzrdirs is not supported yet."""
 
135
        return False
 
136
 
 
137
    def needs_format_conversion(self, format=None):
 
138
        """Upgrading of remote bzrdirs is not supported yet."""
 
139
        return False
 
140
 
 
141
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
142
        self._ensure_real()
 
143
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
144
            basis=basis, force_new_repo=force_new_repo)
 
145
 
 
146
    #def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
147
    #    self._ensure_real()
 
148
    #    return self._real_bzrdir.sprout(url, revision_id=revision_id,
 
149
    #        basis=basis, force_new_repo=force_new_repo)
 
150
 
 
151
 
 
152
class RemoteRepositoryFormat(repository.RepositoryFormat):
 
153
    """Format for repositories accessed over rpc.
 
154
 
 
155
    Instances of this repository are represented by RemoteRepository
 
156
    instances.
 
157
    """
 
158
 
 
159
    _matchingbzrdir = RemoteBzrDirFormat
 
160
 
 
161
    def initialize(self, a_bzrdir, shared=False):
 
162
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
163
        return a_bzrdir.create_repository(shared=shared)
 
164
    
 
165
    def open(self, a_bzrdir):
 
166
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
167
        return a_bzrdir.open_repository()
 
168
 
 
169
    def get_format_description(self):
 
170
        return 'bzr remote repository'
 
171
 
 
172
    def __eq__(self, other):
 
173
        return self.__class__ == other.__class__
 
174
 
 
175
    rich_root_data = False
 
176
 
 
177
 
 
178
class RemoteRepository(object):
 
179
    """Repository accessed over rpc.
 
180
 
 
181
    For the moment everything is delegated to IO-like operations over
 
182
    the transport.
 
183
    """
 
184
 
 
185
    def __init__(self, remote_bzrdir, real_repository=None, _client=None):
 
186
        """Create a RemoteRepository instance.
 
187
        
 
188
        :param remote_bzrdir: The bzrdir hosting this repository.
 
189
        :param real_repository: If not None, a local implementation of the
 
190
            repository logic for the repository, usually accessing the data
 
191
            via the VFS.
 
192
        :param _client: Private testing parameter - override the smart client
 
193
            to be used by the repository.
 
194
        """
 
195
        if real_repository:
 
196
            self._real_repository = real_repository
 
197
        else:
 
198
            self._real_repository = None
 
199
        self.bzrdir = remote_bzrdir
 
200
        if _client is None:
 
201
            self._client = client.SmartClient(self.bzrdir._medium)
 
202
        else:
 
203
            self._client = _client
 
204
        self._format = RemoteRepositoryFormat()
 
205
        self._lock_mode = None
 
206
        self._lock_token = None
 
207
        self._lock_count = 0
 
208
        self._leave_lock = False
 
209
 
 
210
    def _ensure_real(self):
 
211
        """Ensure that there is a _real_repository set.
 
212
 
 
213
        used before calls to self._real_repository.
 
214
        """
 
215
        if not self._real_repository:
 
216
            self.bzrdir._ensure_real()
 
217
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
218
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
 
219
 
 
220
    def get_revision_graph(self, revision_id=None):
 
221
        """See Repository.get_revision_graph()."""
 
222
        if revision_id is None:
 
223
            revision_id = ''
 
224
        elif revision_id == NULL_REVISION:
 
225
            return {}
 
226
 
 
227
        path = self.bzrdir._path_for_remote_call(self._client)
 
228
        assert type(revision_id) is str
 
229
        response = self._client.call2(
 
230
            'Repository.get_revision_graph', path, revision_id)
 
231
        assert response[0][0] in ('ok', 'nosuchrevision'), 'unexpected response code %s' % (response[0],)
 
232
        if response[0][0] == 'ok':
 
233
            coded = response[1].read_body_bytes()
 
234
            lines = coded.split('\n')
 
235
            revision_graph = {}
 
236
            # FIXME
 
237
            for line in lines:
 
238
                d = list(line.split())
 
239
                revision_graph[d[0]] = d[1:]
 
240
                
 
241
            return revision_graph
 
242
        else:
 
243
            response_body = response[1].read_body_bytes()
 
244
            assert response_body == ''
 
245
            raise NoSuchRevision(self, revision_id)
 
246
 
 
247
    def has_revision(self, revision_id):
 
248
        """See Repository.has_revision()."""
 
249
        if revision_id is None:
 
250
            # The null revision is always present.
 
251
            return True
 
252
        path = self.bzrdir._path_for_remote_call(self._client)
 
253
        response = self._client.call('Repository.has_revision', path, revision_id)
 
254
        assert response[0] in ('ok', 'no'), 'unexpected response code %s' % (response,)
 
255
        return response[0] == 'ok'
 
256
 
 
257
    def gather_stats(self, revid=None, committers=None):
 
258
        """See Repository.gather_stats()."""
 
259
        path = self.bzrdir._path_for_remote_call(self._client)
 
260
        if revid in (None, NULL_REVISION):
 
261
            fmt_revid = ''
 
262
        else:
 
263
            fmt_revid = revid
 
264
        if committers is None or not committers:
 
265
            fmt_committers = 'no'
 
266
        else:
 
267
            fmt_committers = 'yes'
 
268
        response = self._client.call2('Repository.gather_stats', path,
 
269
                                      fmt_revid, fmt_committers)
 
270
        assert response[0][0] == 'ok', \
 
271
            'unexpected response code %s' % (response[0],)
 
272
 
 
273
        body = response[1].read_body_bytes()
 
274
        result = {}
 
275
        for line in body.split('\n'):
 
276
            if not line:
 
277
                continue
 
278
            key, val_text = line.split(':')
 
279
            if key in ('revisions', 'size', 'committers'):
 
280
                result[key] = int(val_text)
 
281
            elif key in ('firstrev', 'latestrev'):
 
282
                values = val_text.split(' ')[1:]
 
283
                result[key] = (float(values[0]), long(values[1]))
 
284
 
 
285
        return result
 
286
 
 
287
    def get_physical_lock_status(self):
 
288
        """See Repository.get_physical_lock_status()."""
 
289
        return False
 
290
 
 
291
    def is_shared(self):
 
292
        """See Repository.is_shared()."""
 
293
        path = self.bzrdir._path_for_remote_call(self._client)
 
294
        response = self._client.call('Repository.is_shared', path)
 
295
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
296
        return response[0] == 'yes'
 
297
 
 
298
    def lock_read(self):
 
299
        # wrong eventually - want a local lock cache context
 
300
        if not self._lock_mode:
 
301
            self._lock_mode = 'r'
 
302
            self._lock_count = 1
 
303
            if self._real_repository is not None:
 
304
                self._real_repository.lock_read()
 
305
        else:
 
306
            self._lock_count += 1
 
307
 
 
308
    def _remote_lock_write(self, token):
 
309
        path = self.bzrdir._path_for_remote_call(self._client)
 
310
        if token is None:
 
311
            token = ''
 
312
        response = self._client.call('Repository.lock_write', path, token)
 
313
        if response[0] == 'ok':
 
314
            ok, token = response
 
315
            return token
 
316
        elif response[0] == 'LockContention':
 
317
            raise errors.LockContention('(remote lock)')
 
318
        elif response[0] == 'UnlockableTransport':
 
319
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
320
        else:
 
321
            assert False, 'unexpected response code %s' % (response,)
 
322
 
 
323
    def lock_write(self, token=None):
 
324
        if not self._lock_mode:
 
325
            self._lock_token = self._remote_lock_write(token)
 
326
            assert self._lock_token, 'Remote server did not return a token!'
 
327
            if self._real_repository is not None:
 
328
                self._real_repository.lock_write(token=self._lock_token)
 
329
            if token is not None:
 
330
                self._leave_lock = True
 
331
            else:
 
332
                self._leave_lock = False
 
333
            self._lock_mode = 'w'
 
334
            self._lock_count = 1
 
335
        elif self._lock_mode == 'r':
 
336
            raise errors.ReadOnlyError(self)
 
337
        else:
 
338
            self._lock_count += 1
 
339
        return self._lock_token
 
340
 
 
341
    def leave_lock_in_place(self):
 
342
        self._leave_lock = True
 
343
 
 
344
    def dont_leave_lock_in_place(self):
 
345
        self._leave_lock = False
 
346
 
 
347
    def _set_real_repository(self, repository):
 
348
        """Set the _real_repository for this repository.
 
349
 
 
350
        :param repository: The repository to fallback to for non-hpss
 
351
            implemented operations.
 
352
        """
 
353
        self._real_repository = repository
 
354
        if self._lock_mode == 'w':
 
355
            # if we are already locked, the real repository must be able to
 
356
            # acquire the lock with our token.
 
357
            self._real_repository.lock_write(self._lock_token)
 
358
        elif self._lock_mode == 'r':
 
359
            self._real_repository.lock_read()
 
360
 
 
361
    def _unlock(self, token):
 
362
        path = self.bzrdir._path_for_remote_call(self._client)
 
363
        response = self._client.call('Repository.unlock', path, token)
 
364
        if response == ('ok',):
 
365
            return
 
366
        elif response[0] == 'TokenMismatch':
 
367
            raise errors.TokenMismatch(token, '(remote token)')
 
368
        else:
 
369
            assert False, 'unexpected response code %s' % (response,)
 
370
 
 
371
    def unlock(self):
 
372
        self._lock_count -= 1
 
373
        if not self._lock_count:
 
374
            mode = self._lock_mode
 
375
            self._lock_mode = None
 
376
            if self._real_repository is not None:
 
377
                self._real_repository.unlock()
 
378
            if mode != 'w':
 
379
                return
 
380
            assert self._lock_token, 'Locked, but no token!'
 
381
            token = self._lock_token
 
382
            self._lock_token = None
 
383
            if not self._leave_lock:
 
384
                self._unlock(token)
 
385
 
 
386
    def break_lock(self):
 
387
        # should hand off to the network
 
388
        self._ensure_real()
 
389
        return self._real_repository.break_lock()
 
390
 
 
391
    ### These methods are just thin shims to the VFS object for now.
 
392
 
 
393
    def revision_tree(self, revision_id):
 
394
        self._ensure_real()
 
395
        return self._real_repository.revision_tree(revision_id)
 
396
 
 
397
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
398
                           timezone=None, committer=None, revprops=None,
 
399
                           revision_id=None):
 
400
        # FIXME: It ought to be possible to call this without immediately
 
401
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
402
        self._ensure_real()
 
403
        builder = self._real_repository.get_commit_builder(branch, parents,
 
404
                config, timestamp=timestamp, timezone=timezone,
 
405
                committer=committer, revprops=revprops, revision_id=revision_id)
 
406
        # Make the builder use this RemoteRepository rather than the real one.
 
407
        builder.repository = self
 
408
        return builder
 
409
 
 
410
    @needs_write_lock
 
411
    def add_inventory(self, revid, inv, parents):
 
412
        self._ensure_real()
 
413
        return self._real_repository.add_inventory(revid, inv, parents)
 
414
 
 
415
    @needs_write_lock
 
416
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
417
        self._ensure_real()
 
418
        return self._real_repository.add_revision(
 
419
            rev_id, rev, inv=inv, config=config)
 
420
 
 
421
    @needs_read_lock
 
422
    def get_inventory(self, revision_id):
 
423
        self._ensure_real()
 
424
        return self._real_repository.get_inventory(revision_id)
 
425
 
 
426
    @needs_read_lock
 
427
    def get_revision(self, revision_id):
 
428
        self._ensure_real()
 
429
        return self._real_repository.get_revision(revision_id)
 
430
 
 
431
    @property
 
432
    def weave_store(self):
 
433
        self._ensure_real()
 
434
        return self._real_repository.weave_store
 
435
 
 
436
    def get_transaction(self):
 
437
        self._ensure_real()
 
438
        return self._real_repository.get_transaction()
 
439
 
 
440
    @needs_read_lock
 
441
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
442
        self._ensure_real()
 
443
        return self._real_repository.clone(
 
444
            a_bzrdir, revision_id=revision_id, basis=basis)
 
445
 
 
446
    def make_working_trees(self):
 
447
        return False
 
448
 
 
449
    def fetch(self, source, revision_id=None, pb=None):
 
450
        self._ensure_real()
 
451
        return self._real_repository.fetch(
 
452
            source, revision_id=revision_id, pb=pb)
 
453
 
 
454
    @property
 
455
    def control_weaves(self):
 
456
        self._ensure_real()
 
457
        return self._real_repository.control_weaves
 
458
 
 
459
    @needs_read_lock
 
460
    def get_ancestry(self, revision_id):
 
461
        self._ensure_real()
 
462
        return self._real_repository.get_ancestry(revision_id)
 
463
 
 
464
    @needs_read_lock
 
465
    def get_inventory_weave(self):
 
466
        self._ensure_real()
 
467
        return self._real_repository.get_inventory_weave()
 
468
 
 
469
    def fileids_altered_by_revision_ids(self, revision_ids):
 
470
        self._ensure_real()
 
471
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
472
 
 
473
    @needs_read_lock
 
474
    def get_signature_text(self, revision_id):
 
475
        self._ensure_real()
 
476
        return self._real_repository.get_signature_text(revision_id)
 
477
 
 
478
    @needs_read_lock
 
479
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
480
        self._ensure_real()
 
481
        return self._real_repository.get_revision_graph_with_ghosts(
 
482
            revision_ids=revision_ids)
 
483
 
 
484
    @needs_read_lock
 
485
    def get_inventory_xml(self, revision_id):
 
486
        self._ensure_real()
 
487
        return self._real_repository.get_inventory_xml(revision_id)
 
488
 
 
489
    def deserialise_inventory(self, revision_id, xml):
 
490
        self._ensure_real()
 
491
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
492
 
 
493
    def reconcile(self, other=None, thorough=False):
 
494
        self._ensure_real()
 
495
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
496
        
 
497
    def all_revision_ids(self):
 
498
        self._ensure_real()
 
499
        return self._real_repository.all_revision_ids()
 
500
    
 
501
    @needs_read_lock
 
502
    def get_deltas_for_revisions(self, revisions):
 
503
        self._ensure_real()
 
504
        return self._real_repository.get_deltas_for_revisions(revisions)
 
505
 
 
506
    @needs_read_lock
 
507
    def get_revision_delta(self, revision_id):
 
508
        self._ensure_real()
 
509
        return self._real_repository.get_revision_delta(revision_id)
 
510
 
 
511
    @needs_read_lock
 
512
    def revision_trees(self, revision_ids):
 
513
        self._ensure_real()
 
514
        return self._real_repository.revision_trees(revision_ids)
 
515
 
 
516
    @needs_read_lock
 
517
    def get_revision_reconcile(self, revision_id):
 
518
        self._ensure_real()
 
519
        return self._real_repository.get_revision_reconcile(revision_id)
 
520
 
 
521
    @needs_read_lock
 
522
    def check(self, revision_ids):
 
523
        self._ensure_real()
 
524
        return self._real_repository.check(revision_ids)
 
525
 
 
526
    def copy_content_into(self, destination, revision_id=None, basis=None):
 
527
        self._ensure_real()
 
528
        return self._real_repository.copy_content_into(
 
529
            destination, revision_id=revision_id, basis=basis)
 
530
 
 
531
    def set_make_working_trees(self, new_value):
 
532
        raise NotImplementedError(self.set_make_working_trees)
 
533
 
 
534
    @needs_write_lock
 
535
    def sign_revision(self, revision_id, gpg_strategy):
 
536
        self._ensure_real()
 
537
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
538
 
 
539
    @needs_read_lock
 
540
    def get_revisions(self, revision_ids):
 
541
        self._ensure_real()
 
542
        return self._real_repository.get_revisions(revision_ids)
 
543
 
 
544
    def supports_rich_root(self):
 
545
        self._ensure_real()
 
546
        return self._real_repository.supports_rich_root()
 
547
 
 
548
    def iter_reverse_revision_history(self, revision_id):
 
549
        self._ensure_real()
 
550
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
551
 
 
552
 
 
553
class RemoteBranchLockableFiles(object):
 
554
    """A 'LockableFiles' implementation that talks to a smart server.
 
555
    
 
556
    This is not a public interface class.
 
557
    """
 
558
 
 
559
    def __init__(self, bzrdir, _client):
 
560
        self.bzrdir = bzrdir
 
561
        self._client = _client
 
562
 
 
563
    def get(self, path):
 
564
        """'get' a remote path as per the LockableFiles interface.
 
565
 
 
566
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
567
             just retrieve a file, instead we ask the smart server to generate
 
568
             a configuration for us - which is retrieved as an INI file.
 
569
        """
 
570
        assert path == 'branch.conf'
 
571
        path = self.bzrdir._path_for_remote_call(self._client)
 
572
        response = self._client.call2('Branch.get_config_file', path)
 
573
        assert response[0][0] == 'ok', \
 
574
            'unexpected response code %s' % (response[0],)
 
575
        return StringIO(response[1].read_body_bytes())
 
576
 
 
577
 
 
578
class RemoteBranchFormat(branch.BranchFormat):
 
579
 
 
580
    def get_format_description(self):
 
581
        return 'Remote BZR Branch'
 
582
 
 
583
    def get_format_string(self):
 
584
        return 'Remote BZR Branch'
 
585
 
 
586
    def open(self, a_bzrdir):
 
587
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
588
        return a_bzrdir.open_branch()
 
589
 
 
590
    def initialize(self, a_bzrdir):
 
591
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
592
        return a_bzrdir.create_branch()
 
593
 
 
594
 
 
595
class RemoteBranch(branch.Branch):
 
596
    """Branch stored on a server accessed by HPSS RPC.
 
597
 
 
598
    At the moment most operations are mapped down to simple file operations.
 
599
    """
 
600
 
 
601
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
602
        _client=None):
 
603
        """Create a RemoteBranch instance.
 
604
 
 
605
        :param real_branch: An optional local implementation of the branch
 
606
            format, usually accessing the data via the VFS.
 
607
        :param _client: Private parameter for testing.
 
608
        """
 
609
        self.bzrdir = remote_bzrdir
 
610
        if _client is not None:
 
611
            self._client = _client
 
612
        else:
 
613
            self._client = client.SmartClient(self.bzrdir._medium)
 
614
        self.repository = remote_repository
 
615
        if real_branch is not None:
 
616
            self._real_branch = real_branch
 
617
            # Give the remote repository the matching real repo.
 
618
            self.repository._set_real_repository(self._real_branch.repository)
 
619
            # Give the branch the remote repository to let fast-pathing happen.
 
620
            self._real_branch.repository = self.repository
 
621
        else:
 
622
            self._real_branch = None
 
623
        # Fill out expected attributes of branch for bzrlib api users.
 
624
        self._format = RemoteBranchFormat()
 
625
        self.base = self.bzrdir.root_transport.base
 
626
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
627
        self._lock_mode = None
 
628
        self._lock_token = None
 
629
        self._lock_count = 0
 
630
        self._leave_lock = False
 
631
 
 
632
    def _ensure_real(self):
 
633
        """Ensure that there is a _real_branch set.
 
634
 
 
635
        used before calls to self._real_branch.
 
636
        """
 
637
        if not self._real_branch:
 
638
            assert vfs.vfs_enabled()
 
639
            self.bzrdir._ensure_real()
 
640
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
641
            # Give the remote repository the matching real repo.
 
642
            self.repository._set_real_repository(self._real_branch.repository)
 
643
            # Give the branch the remote repository to let fast-pathing happen.
 
644
            self._real_branch.repository = self.repository
 
645
            # XXX: deal with _lock_mode == 'w'
 
646
            if self._lock_mode == 'r':
 
647
                self._real_branch.lock_read()
 
648
 
 
649
    def get_physical_lock_status(self):
 
650
        """See Branch.get_physical_lock_status()."""
 
651
        # should be an API call to the server, as branches must be lockable.
 
652
        self._ensure_real()
 
653
        return self._real_branch.get_physical_lock_status()
 
654
 
 
655
    def lock_read(self):
 
656
        if not self._lock_mode:
 
657
            self._lock_mode = 'r'
 
658
            self._lock_count = 1
 
659
            if self._real_branch is not None:
 
660
                self._real_branch.lock_read()
 
661
        else:
 
662
            self._lock_count += 1
 
663
 
 
664
    def _remote_lock_write(self, tokens):
 
665
        if tokens is None:
 
666
            branch_token = repo_token = ''
 
667
        else:
 
668
            branch_token, repo_token = tokens
 
669
        path = self.bzrdir._path_for_remote_call(self._client)
 
670
        response = self._client.call('Branch.lock_write', path, branch_token,
 
671
                                     repo_token)
 
672
        if response[0] == 'ok':
 
673
            ok, branch_token, repo_token = response
 
674
            return branch_token, repo_token
 
675
        elif response[0] == 'LockContention':
 
676
            raise errors.LockContention('(remote lock)')
 
677
        elif response[0] == 'TokenMismatch':
 
678
            raise errors.TokenMismatch(tokens, '(remote tokens)')
 
679
        elif response[0] == 'UnlockableTransport':
 
680
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
681
        else:
 
682
            assert False, 'unexpected response code %r' % (response,)
 
683
            
 
684
    def lock_write(self, tokens=None):
 
685
        if not self._lock_mode:
 
686
            remote_tokens = self._remote_lock_write(tokens)
 
687
            self._lock_token, self._repo_lock_token = remote_tokens
 
688
            assert self._lock_token, 'Remote server did not return a token!'
 
689
            # TODO: We really, really, really don't want to call _ensure_real
 
690
            # here, but it's the easiest way to ensure coherency between the
 
691
            # state of the RemoteBranch and RemoteRepository objects and the
 
692
            # physical locks.  If we don't materialise the real objects here,
 
693
            # then getting everything in the right state later is complex, so
 
694
            # for now we just do it the lazy way.
 
695
            #   -- Andrew Bennetts, 2007-02-22.
 
696
            self._ensure_real()
 
697
            if self._real_branch is not None:
 
698
                self._real_branch.lock_write(tokens=remote_tokens)
 
699
            if tokens is not None:
 
700
                self._leave_lock = True
 
701
            else:
 
702
                # XXX: this case seems to be unreachable; tokens cannot be None.
 
703
                self._leave_lock = False
 
704
            self._lock_mode = 'w'
 
705
            self._lock_count = 1
 
706
        elif self._lock_mode == 'r':
 
707
            raise errors.ReadOnlyTransaction
 
708
        else:
 
709
            if tokens is not None:
 
710
                # Tokens were given to lock_write, and we're relocking, so check
 
711
                # that the given tokens actually match the ones we already have.
 
712
                held_tokens = (self._lock_token, self._repo_lock_token)
 
713
                if tokens != held_tokens:
 
714
                    raise errors.TokenMismatch(str(tokens), str(held_tokens))
 
715
            self._lock_count += 1
 
716
        return self._lock_token, self._repo_lock_token
 
717
 
 
718
    def _unlock(self, branch_token, repo_token):
 
719
        path = self.bzrdir._path_for_remote_call(self._client)
 
720
        response = self._client.call('Branch.unlock', path, branch_token,
 
721
                                     repo_token)
 
722
        if response == ('ok',):
 
723
            return
 
724
        elif response[0] == 'TokenMismatch':
 
725
            raise errors.TokenMismatch(
 
726
                str((branch_token, repo_token)), '(remote tokens)')
 
727
        else:
 
728
            assert False, 'unexpected response code %s' % (response,)
 
729
 
 
730
    def unlock(self):
 
731
        self._lock_count -= 1
 
732
        if not self._lock_count:
 
733
            mode = self._lock_mode
 
734
            self._lock_mode = None
 
735
            if self._real_branch is not None:
 
736
                if not self._leave_lock:
 
737
                    # If this RemoteBranch will remove the physical lock for the
 
738
                    # repository, make sure the _real_branch doesn't do it
 
739
                    # first.  (Because the _real_branch's repository is set to
 
740
                    # be the RemoteRepository.)
 
741
                    self._real_branch.repository.leave_lock_in_place()
 
742
                self._real_branch.unlock()
 
743
            if mode != 'w':
 
744
                return
 
745
            assert self._lock_token, 'Locked, but no token!'
 
746
            branch_token = self._lock_token
 
747
            repo_token = self._repo_lock_token
 
748
            self._lock_token = None
 
749
            self._repo_lock_token = None
 
750
            if not self._leave_lock:
 
751
                self._unlock(branch_token, repo_token)
 
752
 
 
753
    def break_lock(self):
 
754
        self._ensure_real()
 
755
        return self._real_branch.break_lock()
 
756
 
 
757
    def leave_lock_in_place(self):
 
758
        self._leave_lock = True
 
759
 
 
760
    def dont_leave_lock_in_place(self):
 
761
        self._leave_lock = False
 
762
 
 
763
    def last_revision_info(self):
 
764
        """See Branch.last_revision_info()."""
 
765
        path = self.bzrdir._path_for_remote_call(self._client)
 
766
        response = self._client.call('Branch.last_revision_info', path)
 
767
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
768
        revno = int(response[1])
 
769
        last_revision = response[2]
 
770
        if last_revision == '':
 
771
            last_revision = NULL_REVISION
 
772
        return (revno, last_revision)
 
773
 
 
774
    def revision_history(self):
 
775
        """See Branch.revision_history()."""
 
776
        # XXX: TODO: this does not cache the revision history for the duration
 
777
        # of a lock, which is a bug - see the code for regular branches
 
778
        # for details.
 
779
        path = self.bzrdir._path_for_remote_call(self._client)
 
780
        response = self._client.call2('Branch.revision_history', path)
 
781
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
782
        result = response[1].read_body_bytes().split('\x00')
 
783
        if result == ['']:
 
784
            return []
 
785
        return result
 
786
 
 
787
    @needs_write_lock
 
788
    def set_revision_history(self, rev_history):
 
789
        # Send just the tip revision of the history; the server will generate
 
790
        # the full history from that.  If the revision doesn't exist in this
 
791
        # branch, NoSuchRevision will be raised.
 
792
        path = self.bzrdir._path_for_remote_call(self._client)
 
793
        if rev_history == []:
 
794
            rev_id = ''
 
795
        else:
 
796
            rev_id = rev_history[-1]
 
797
        response = self._client.call('Branch.set_last_revision',
 
798
            path, self._lock_token, self._repo_lock_token, rev_id)
 
799
        if response[0] == 'NoSuchRevision':
 
800
            raise NoSuchRevision(self, rev_id)
 
801
        else:
 
802
            assert response == ('ok',), (
 
803
                'unexpected response code %r' % (response,))
 
804
 
 
805
    def get_parent(self):
 
806
        self._ensure_real()
 
807
        return self._real_branch.get_parent()
 
808
        
 
809
    def set_parent(self, url):
 
810
        self._ensure_real()
 
811
        return self._real_branch.set_parent(url)
 
812
        
 
813
    def get_config(self):
 
814
        return RemoteBranchConfig(self)
 
815
 
 
816
    def sprout(self, to_bzrdir, revision_id=None):
 
817
        # Like Branch.sprout, except that it sprouts a branch in the default
 
818
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
819
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
820
        # to_bzrdir.create_branch...
 
821
        self._ensure_real()
 
822
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
823
        self._real_branch.copy_content_into(result, revision_id=revision_id)
 
824
        result.set_parent(self.bzrdir.root_transport.base)
 
825
        return result
 
826
 
 
827
    @needs_write_lock
 
828
    def append_revision(self, *revision_ids):
 
829
        self._ensure_real()
 
830
        return self._real_branch.append_revision(*revision_ids)
 
831
 
 
832
    @needs_write_lock
 
833
    def pull(self, source, overwrite=False, stop_revision=None):
 
834
        self._ensure_real()
 
835
        self._real_branch.pull(
 
836
            source, overwrite=overwrite, stop_revision=stop_revision)
 
837
 
 
838
    @needs_read_lock
 
839
    def push(self, target, overwrite=False, stop_revision=None):
 
840
        self._ensure_real()
 
841
        self._real_branch.push(
 
842
            target, overwrite=overwrite, stop_revision=stop_revision)
 
843
 
 
844
    def is_locked(self):
 
845
        return self._lock_count >= 1
 
846
 
 
847
    def set_last_revision_info(self, revno, revision_id):
 
848
        self._ensure_real()
 
849
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
850
 
 
851
    def generate_revision_history(self, revision_id, last_rev=None,
 
852
                                  other_branch=None):
 
853
        self._ensure_real()
 
854
        return self._real_branch.generate_revision_history(
 
855
            revision_id, last_rev=last_rev, other_branch=other_branch)
 
856
 
 
857
 
 
858
class RemoteWorkingTree(object):
 
859
 
 
860
    def __init__(self, remote_bzrdir, real_workingtree):
 
861
        self.real_workingtree = real_workingtree
 
862
        self.bzrdir = remote_bzrdir
 
863
 
 
864
    def __getattr__(self, name):
 
865
        # XXX: temporary way to lazily delegate everything to the real
 
866
        # workingtree
 
867
        return getattr(self.real_workingtree, name)
 
868
 
 
869
 
 
870
class RemoteBranchConfig(BranchConfig):
 
871
 
 
872
    def username(self):
 
873
        self.branch._ensure_real()
 
874
        return self.branch._real_branch.get_config().username()
 
875
 
 
876
    def _get_branch_data_config(self):
 
877
        self.branch._ensure_real()
 
878
        if self._branch_data_config is None:
 
879
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
880
        return self._branch_data_config
 
881