/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-28 07:08:25 UTC
  • mfrom: (2018.13.1 hpss)
  • mto: (2018.5.80 hpss)
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070228070825-q2dvkjb0a11ouhtx
Update to current hpss branch?  Fix lots of test failures.

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