/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-04-17 09:50:11 UTC
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070417095011-my9qj2tvugvslhhg
Remove XXX

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