/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

Merge bzr.dev.

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