/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

Various small changes in aid of making tests pass (including deleting one invalid test).

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
        else:
 
319
            assert False, 'unexpected response code %s' % (response,)
 
320
 
 
321
    def lock_write(self, token=None):
 
322
        if not self._lock_mode:
 
323
            self._lock_token = self._remote_lock_write(token)
 
324
            assert self._lock_token, 'Remote server did not return a token!'
 
325
            if self._real_repository is not None:
 
326
                self._real_repository.lock_write(token=self._lock_token)
 
327
            if token is not None:
 
328
                self._leave_lock = True
 
329
            else:
 
330
                self._leave_lock = False
 
331
            self._lock_mode = 'w'
 
332
            self._lock_count = 1
 
333
        elif self._lock_mode == 'r':
 
334
            raise errors.ReadOnlyError(self)
 
335
        else:
 
336
            self._lock_count += 1
 
337
        return self._lock_token
 
338
 
 
339
    def leave_lock_in_place(self):
 
340
        self._leave_lock = True
 
341
 
 
342
    def dont_leave_lock_in_place(self):
 
343
        self._leave_lock = False
 
344
 
 
345
    def _set_real_repository(self, repository):
 
346
        """Set the _real_repository for this repository.
 
347
 
 
348
        :param repository: The repository to fallback to for non-hpss
 
349
            implemented operations.
 
350
        """
 
351
        self._real_repository = repository
 
352
        if self._lock_mode == 'w':
 
353
            # if we are already locked, the real repository must be able to
 
354
            # acquire the lock with our token.
 
355
            self._real_repository.lock_write(self._lock_token)
 
356
        elif self._lock_mode == 'r':
 
357
            self._real_repository.lock_read()
 
358
 
 
359
    def _unlock(self, token):
 
360
        path = self.bzrdir._path_for_remote_call(self._client)
 
361
        response = self._client.call('Repository.unlock', path, token)
 
362
        if response == ('ok',):
 
363
            return
 
364
        elif response[0] == 'TokenMismatch':
 
365
            raise errors.TokenMismatch(token, '(remote token)')
 
366
        else:
 
367
            assert False, 'unexpected response code %s' % (response,)
 
368
 
 
369
    def unlock(self):
 
370
        self._lock_count -= 1
 
371
        if not self._lock_count:
 
372
            mode = self._lock_mode
 
373
            self._lock_mode = None
 
374
            if self._real_repository is not None:
 
375
                self._real_repository.unlock()
 
376
            if mode != 'w':
 
377
                return
 
378
            assert self._lock_token, 'Locked, but no token!'
 
379
            token = self._lock_token
 
380
            self._lock_token = None
 
381
            if not self._leave_lock:
 
382
                self._unlock(token)
 
383
 
 
384
    def break_lock(self):
 
385
        # should hand off to the network
 
386
        self._ensure_real()
 
387
        return self._real_repository.break_lock()
 
388
 
 
389
    ### These methods are just thin shims to the VFS object for now.
 
390
 
 
391
    def revision_tree(self, revision_id):
 
392
        self._ensure_real()
 
393
        return self._real_repository.revision_tree(revision_id)
 
394
 
 
395
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
396
                           timezone=None, committer=None, revprops=None,
 
397
                           revision_id=None):
 
398
        # FIXME: It ought to be possible to call this without immediately
 
399
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
400
        self._ensure_real()
 
401
        builder = self._real_repository.get_commit_builder(branch, parents,
 
402
                config, timestamp=timestamp, timezone=timezone,
 
403
                committer=committer, revprops=revprops, revision_id=revision_id)
 
404
        # Make the builder use this RemoteRepository rather than the real one.
 
405
        builder.repository = self
 
406
        return builder
 
407
 
 
408
    @needs_write_lock
 
409
    def add_inventory(self, revid, inv, parents):
 
410
        self._ensure_real()
 
411
        return self._real_repository.add_inventory(revid, inv, parents)
 
412
 
 
413
    @needs_write_lock
 
414
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
415
        self._ensure_real()
 
416
        return self._real_repository.add_revision(
 
417
            rev_id, rev, inv=inv, config=config)
 
418
 
 
419
    @needs_read_lock
 
420
    def get_inventory(self, revision_id):
 
421
        self._ensure_real()
 
422
        return self._real_repository.get_inventory(revision_id)
 
423
 
 
424
    @needs_read_lock
 
425
    def get_revision(self, revision_id):
 
426
        self._ensure_real()
 
427
        return self._real_repository.get_revision(revision_id)
 
428
 
 
429
    @property
 
430
    def weave_store(self):
 
431
        self._ensure_real()
 
432
        return self._real_repository.weave_store
 
433
 
 
434
    def get_transaction(self):
 
435
        self._ensure_real()
 
436
        return self._real_repository.get_transaction()
 
437
 
 
438
    @needs_read_lock
 
439
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
440
        self._ensure_real()
 
441
        return self._real_repository.clone(
 
442
            a_bzrdir, revision_id=revision_id, basis=basis)
 
443
 
 
444
    def make_working_trees(self):
 
445
        return False
 
446
 
 
447
    def fetch(self, source, revision_id=None, pb=None):
 
448
        self._ensure_real()
 
449
        return self._real_repository.fetch(
 
450
            source, revision_id=revision_id, pb=pb)
 
451
 
 
452
    @property
 
453
    def control_weaves(self):
 
454
        self._ensure_real()
 
455
        return self._real_repository.control_weaves
 
456
 
 
457
    @needs_read_lock
 
458
    def get_ancestry(self, revision_id):
 
459
        self._ensure_real()
 
460
        return self._real_repository.get_ancestry(revision_id)
 
461
 
 
462
    @needs_read_lock
 
463
    def get_inventory_weave(self):
 
464
        self._ensure_real()
 
465
        return self._real_repository.get_inventory_weave()
 
466
 
 
467
    def fileids_altered_by_revision_ids(self, revision_ids):
 
468
        self._ensure_real()
 
469
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
 
470
 
 
471
    @needs_read_lock
 
472
    def get_signature_text(self, revision_id):
 
473
        self._ensure_real()
 
474
        return self._real_repository.get_signature_text(revision_id)
 
475
 
 
476
    @needs_read_lock
 
477
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
478
        self._ensure_real()
 
479
        return self._real_repository.get_revision_graph_with_ghosts(
 
480
            revision_ids=revision_ids)
 
481
 
 
482
    @needs_read_lock
 
483
    def get_inventory_xml(self, revision_id):
 
484
        self._ensure_real()
 
485
        return self._real_repository.get_inventory_xml(revision_id)
 
486
 
 
487
    def deserialise_inventory(self, revision_id, xml):
 
488
        self._ensure_real()
 
489
        return self._real_repository.deserialise_inventory(revision_id, xml)
 
490
 
 
491
    def reconcile(self, other=None, thorough=False):
 
492
        self._ensure_real()
 
493
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
494
        
 
495
    def all_revision_ids(self):
 
496
        self._ensure_real()
 
497
        return self._real_repository.all_revision_ids()
 
498
    
 
499
    @needs_read_lock
 
500
    def get_deltas_for_revisions(self, revisions):
 
501
        self._ensure_real()
 
502
        return self._real_repository.get_deltas_for_revisions(revisions)
 
503
 
 
504
    @needs_read_lock
 
505
    def get_revision_delta(self, revision_id):
 
506
        self._ensure_real()
 
507
        return self._real_repository.get_revision_delta(revision_id)
 
508
 
 
509
    @needs_read_lock
 
510
    def revision_trees(self, revision_ids):
 
511
        self._ensure_real()
 
512
        return self._real_repository.revision_trees(revision_ids)
 
513
 
 
514
    @needs_read_lock
 
515
    def get_revision_reconcile(self, revision_id):
 
516
        self._ensure_real()
 
517
        return self._real_repository.get_revision_reconcile(revision_id)
 
518
 
 
519
    @needs_read_lock
 
520
    def check(self, revision_ids):
 
521
        self._ensure_real()
 
522
        return self._real_repository.check(revision_ids)
 
523
 
 
524
    def copy_content_into(self, destination, revision_id=None, basis=None):
 
525
        self._ensure_real()
 
526
        return self._real_repository.copy_content_into(
 
527
            destination, revision_id=revision_id, basis=basis)
 
528
 
 
529
    def set_make_working_trees(self, new_value):
 
530
        raise NotImplementedError(self.set_make_working_trees)
 
531
 
 
532
    @needs_write_lock
 
533
    def sign_revision(self, revision_id, gpg_strategy):
 
534
        self._ensure_real()
 
535
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
536
 
 
537
    @needs_read_lock
 
538
    def get_revisions(self, revision_ids):
 
539
        self._ensure_real()
 
540
        return self._real_repository.get_revisions(revision_ids)
 
541
 
 
542
    def supports_rich_root(self):
 
543
        self._ensure_real()
 
544
        return self._real_repository.supports_rich_root()
 
545
 
 
546
    def iter_reverse_revision_history(self, revision_id):
 
547
        self._ensure_real()
 
548
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
549
 
 
550
 
 
551
class RemoteBranchLockableFiles(object):
 
552
    """A 'LockableFiles' implementation that talks to a smart server.
 
553
    
 
554
    This is not a public interface class.
 
555
    """
 
556
 
 
557
    def __init__(self, bzrdir, _client):
 
558
        self.bzrdir = bzrdir
 
559
        self._client = _client
 
560
 
 
561
    def get(self, path):
 
562
        """'get' a remote path as per the LockableFiles interface.
 
563
 
 
564
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
565
             just retrieve a file, instead we ask the smart server to generate
 
566
             a configuration for us - which is retrieved as an INI file.
 
567
        """
 
568
        assert path == 'branch.conf'
 
569
        path = self.bzrdir._path_for_remote_call(self._client)
 
570
        response = self._client.call2('Branch.get_config_file', path)
 
571
        assert response[0][0] == 'ok', \
 
572
            'unexpected response code %s' % (response[0],)
 
573
        return StringIO(response[1].read_body_bytes())
 
574
 
 
575
 
 
576
class RemoteBranchFormat(branch.BranchFormat):
 
577
 
 
578
    def get_format_description(self):
 
579
        return 'Remote BZR Branch'
 
580
 
 
581
    def get_format_string(self):
 
582
        return 'Remote BZR Branch'
 
583
 
 
584
    def open(self, a_bzrdir):
 
585
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
586
        return a_bzrdir.open_branch()
 
587
 
 
588
    def initialize(self, a_bzrdir):
 
589
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
590
        return a_bzrdir.create_branch()
 
591
 
 
592
 
 
593
class RemoteBranch(branch.Branch):
 
594
    """Branch stored on a server accessed by HPSS RPC.
 
595
 
 
596
    At the moment most operations are mapped down to simple file operations.
 
597
    """
 
598
 
 
599
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
 
600
        _client=None):
 
601
        """Create a RemoteBranch instance.
 
602
 
 
603
        :param real_branch: An optional local implementation of the branch
 
604
            format, usually accessing the data via the VFS.
 
605
        :param _client: Private parameter for testing.
 
606
        """
 
607
        self.bzrdir = remote_bzrdir
 
608
        if _client is not None:
 
609
            self._client = _client
 
610
        else:
 
611
            self._client = client.SmartClient(self.bzrdir._medium)
 
612
        self.repository = remote_repository
 
613
        if real_branch is not None:
 
614
            self._real_branch = real_branch
 
615
            # Give the remote repository the matching real repo.
 
616
            self.repository._set_real_repository(self._real_branch.repository)
 
617
            # Give the branch the remote repository to let fast-pathing happen.
 
618
            self._real_branch.repository = self.repository
 
619
        else:
 
620
            self._real_branch = None
 
621
        # Fill out expected attributes of branch for bzrlib api users.
 
622
        self._format = RemoteBranchFormat()
 
623
        self.base = self.bzrdir.root_transport.base
 
624
        self.control_files = RemoteBranchLockableFiles(self.bzrdir, self._client)
 
625
        self._lock_mode = None
 
626
        self._lock_token = None
 
627
        self._lock_count = 0
 
628
        self._leave_lock = False
 
629
 
 
630
    def _ensure_real(self):
 
631
        """Ensure that there is a _real_branch set.
 
632
 
 
633
        used before calls to self._real_branch.
 
634
        """
 
635
        if not self._real_branch:
 
636
            assert vfs.vfs_enabled()
 
637
            self.bzrdir._ensure_real()
 
638
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
 
639
            # Give the remote repository the matching real repo.
 
640
            self.repository._set_real_repository(self._real_branch.repository)
 
641
            # Give the branch the remote repository to let fast-pathing happen.
 
642
            self._real_branch.repository = self.repository
 
643
            # XXX: deal with _lock_mode == 'w'
 
644
            if self._lock_mode == 'r':
 
645
                self._real_branch.lock_read()
 
646
 
 
647
    def get_physical_lock_status(self):
 
648
        """See Branch.get_physical_lock_status()."""
 
649
        # should be an API call to the server, as branches must be lockable.
 
650
        self._ensure_real()
 
651
        return self._real_branch.get_physical_lock_status()
 
652
 
 
653
    def lock_read(self):
 
654
        if not self._lock_mode:
 
655
            self._lock_mode = 'r'
 
656
            self._lock_count = 1
 
657
            if self._real_branch is not None:
 
658
                self._real_branch.lock_read()
 
659
        else:
 
660
            self._lock_count += 1
 
661
 
 
662
    def _remote_lock_write(self, tokens):
 
663
        if tokens is None:
 
664
            branch_token = repo_token = ''
 
665
        else:
 
666
            branch_token, repo_token = tokens
 
667
        path = self.bzrdir._path_for_remote_call(self._client)
 
668
        response = self._client.call('Branch.lock_write', path, branch_token,
 
669
                                     repo_token)
 
670
        if response[0] == 'ok':
 
671
            ok, branch_token, repo_token = response
 
672
            return branch_token, repo_token
 
673
        elif response[0] == 'LockContention':
 
674
            raise errors.LockContention('(remote lock)')
 
675
        elif response[0] == 'TokenMismatch':
 
676
            raise errors.TokenMismatch(tokens, '(remote tokens)')
 
677
        else:
 
678
            assert False, 'unexpected response code %r' % (response,)
 
679
            
 
680
    def lock_write(self, tokens=None):
 
681
        if not self._lock_mode:
 
682
            remote_tokens = self._remote_lock_write(tokens)
 
683
            self._lock_token, self._repo_lock_token = remote_tokens
 
684
            assert self._lock_token, 'Remote server did not return a token!'
 
685
            # TODO: We really, really, really don't want to call _ensure_real
 
686
            # here, but it's the easiest way to ensure coherency between the
 
687
            # state of the RemoteBranch and RemoteRepository objects and the
 
688
            # physical locks.  If we don't materialise the real objects here,
 
689
            # then getting everything in the right state later is complex, so
 
690
            # for now we just do it the lazy way.
 
691
            #   -- Andrew Bennetts, 2007-02-22.
 
692
            self._ensure_real()
 
693
            if self._real_branch is not None:
 
694
                self._real_branch.lock_write(tokens=remote_tokens)
 
695
            if tokens is not None:
 
696
                self._leave_lock = True
 
697
            else:
 
698
                # XXX: this case seems to be unreachable; tokens cannot be None.
 
699
                self._leave_lock = False
 
700
            self._lock_mode = 'w'
 
701
            self._lock_count = 1
 
702
        elif self._lock_mode == 'r':
 
703
            raise errors.ReadOnlyTransaction
 
704
        else:
 
705
            if tokens is not None:
 
706
                # Tokens were given to lock_write, and we're relocking, so check
 
707
                # that the given tokens actually match the ones we already have.
 
708
                held_tokens = (self._lock_token, self._repo_lock_token)
 
709
                if tokens != held_tokens:
 
710
                    raise errors.TokenMismatch(str(tokens), str(held_tokens))
 
711
            self._lock_count += 1
 
712
        return self._lock_token, self._repo_lock_token
 
713
 
 
714
    def _unlock(self, branch_token, repo_token):
 
715
        path = self.bzrdir._path_for_remote_call(self._client)
 
716
        response = self._client.call('Branch.unlock', path, branch_token,
 
717
                                     repo_token)
 
718
        if response == ('ok',):
 
719
            return
 
720
        elif response[0] == 'TokenMismatch':
 
721
            raise errors.TokenMismatch(
 
722
                str((branch_token, repo_token)), '(remote tokens)')
 
723
        else:
 
724
            assert False, 'unexpected response code %s' % (response,)
 
725
 
 
726
    def unlock(self):
 
727
        self._lock_count -= 1
 
728
        if not self._lock_count:
 
729
            mode = self._lock_mode
 
730
            self._lock_mode = None
 
731
            if self._real_branch is not None:
 
732
                if not self._leave_lock:
 
733
                    # If this RemoteBranch will remove the physical lock for the
 
734
                    # repository, make sure the _real_branch doesn't do it
 
735
                    # first.  (Because the _real_branch's repository is set to
 
736
                    # be the RemoteRepository.)
 
737
                    self._real_branch.repository.leave_lock_in_place()
 
738
                self._real_branch.unlock()
 
739
            if mode != 'w':
 
740
                return
 
741
            assert self._lock_token, 'Locked, but no token!'
 
742
            branch_token = self._lock_token
 
743
            repo_token = self._repo_lock_token
 
744
            self._lock_token = None
 
745
            self._repo_lock_token = None
 
746
            if not self._leave_lock:
 
747
                self._unlock(branch_token, repo_token)
 
748
 
 
749
    def break_lock(self):
 
750
        self._ensure_real()
 
751
        return self._real_branch.break_lock()
 
752
 
 
753
    def leave_lock_in_place(self):
 
754
        self._leave_lock = True
 
755
 
 
756
    def dont_leave_lock_in_place(self):
 
757
        self._leave_lock = False
 
758
 
 
759
    def last_revision_info(self):
 
760
        """See Branch.last_revision_info()."""
 
761
        path = self.bzrdir._path_for_remote_call(self._client)
 
762
        response = self._client.call('Branch.last_revision_info', path)
 
763
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
764
        revno = int(response[1])
 
765
        last_revision = response[2]
 
766
        if last_revision == '':
 
767
            last_revision = NULL_REVISION
 
768
        return (revno, last_revision)
 
769
 
 
770
    def revision_history(self):
 
771
        """See Branch.revision_history()."""
 
772
        # XXX: TODO: this does not cache the revision history for the duration
 
773
        # of a lock, which is a bug - see the code for regular branches
 
774
        # for details.
 
775
        path = self.bzrdir._path_for_remote_call(self._client)
 
776
        response = self._client.call2('Branch.revision_history', path)
 
777
        assert response[0][0] == 'ok', 'unexpected response code %s' % (response[0],)
 
778
        result = response[1].read_body_bytes().split('\x00')
 
779
        if result == ['']:
 
780
            return []
 
781
        return result
 
782
 
 
783
    @needs_write_lock
 
784
    def set_revision_history(self, rev_history):
 
785
        # Send just the tip revision of the history; the server will generate
 
786
        # the full history from that.  If the revision doesn't exist in this
 
787
        # branch, NoSuchRevision will be raised.
 
788
        path = self.bzrdir._path_for_remote_call(self._client)
 
789
        if rev_history == []:
 
790
            rev_id = ''
 
791
        else:
 
792
            rev_id = rev_history[-1]
 
793
        response = self._client.call('Branch.set_last_revision',
 
794
            path, self._lock_token, self._repo_lock_token, rev_id)
 
795
        if response[0] == 'NoSuchRevision':
 
796
            raise NoSuchRevision(self, rev_id)
 
797
        else:
 
798
            assert response == ('ok',), (
 
799
                'unexpected response code %r' % (response,))
 
800
 
 
801
    def get_parent(self):
 
802
        self._ensure_real()
 
803
        return self._real_branch.get_parent()
 
804
        
 
805
    def set_parent(self, url):
 
806
        self._ensure_real()
 
807
        return self._real_branch.set_parent(url)
 
808
        
 
809
    def get_config(self):
 
810
        return RemoteBranchConfig(self)
 
811
 
 
812
    def sprout(self, to_bzrdir, revision_id=None):
 
813
        # Like Branch.sprout, except that it sprouts a branch in the default
 
814
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
815
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
816
        # to_bzrdir.create_branch...
 
817
        self._ensure_real()
 
818
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
819
        self._real_branch.copy_content_into(result, revision_id=revision_id)
 
820
        result.set_parent(self.bzrdir.root_transport.base)
 
821
        return result
 
822
 
 
823
    @needs_write_lock
 
824
    def append_revision(self, *revision_ids):
 
825
        self._ensure_real()
 
826
        return self._real_branch.append_revision(*revision_ids)
 
827
 
 
828
    @needs_write_lock
 
829
    def pull(self, source, overwrite=False, stop_revision=None):
 
830
        self._ensure_real()
 
831
        self._real_branch.pull(
 
832
            source, overwrite=overwrite, stop_revision=stop_revision)
 
833
 
 
834
    @needs_read_lock
 
835
    def push(self, target, overwrite=False, stop_revision=None):
 
836
        self._ensure_real()
 
837
        self._real_branch.push(
 
838
            target, overwrite=overwrite, stop_revision=stop_revision)
 
839
 
 
840
    def is_locked(self):
 
841
        return self._lock_count >= 1
 
842
 
 
843
    def set_last_revision_info(self, revno, revision_id):
 
844
        self._ensure_real()
 
845
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
846
 
 
847
 
 
848
class RemoteWorkingTree(object):
 
849
 
 
850
    def __init__(self, remote_bzrdir, real_workingtree):
 
851
        self.real_workingtree = real_workingtree
 
852
        self.bzrdir = remote_bzrdir
 
853
 
 
854
    def __getattr__(self, name):
 
855
        # XXX: temporary way to lazily delegate everything to the real
 
856
        # workingtree
 
857
        return getattr(self.real_workingtree, name)
 
858
 
 
859
 
 
860
class RemoteBranchConfig(BranchConfig):
 
861
 
 
862
    def username(self):
 
863
        self.branch._ensure_real()
 
864
        return self.branch._real_branch.get_config().username()
 
865
 
 
866
    def _get_branch_data_config(self):
 
867
        self.branch._ensure_real()
 
868
        if self._branch_data_config is None:
 
869
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
870
        return self._branch_data_config
 
871