/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

Commit final test-case-cleanups branch.

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