~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Ian Clatworthy
  • Date: 2008-03-27 07:51:10 UTC
  • mto: (3311.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3312.
  • Revision ID: ian.clatworthy@canonical.com-20080327075110-afgd7x03ybju06ez
Reduce evangelism in the User Guide

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
# across to run on the server.
19
19
 
20
20
import bz2
 
21
from cStringIO import StringIO
21
22
 
22
23
from bzrlib import (
23
24
    branch,
28
29
    repository,
29
30
    revision,
30
31
    symbol_versioning,
31
 
    urlutils,
32
32
)
33
33
from bzrlib.branch import BranchReferenceFormat
34
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
35
from bzrlib.config import BranchConfig, TreeConfig
35
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
36
 
from bzrlib.errors import (
37
 
    NoSuchRevision,
38
 
    SmartProtocolError,
39
 
    )
 
37
from bzrlib.errors import NoSuchRevision
40
38
from bzrlib.lockable_files import LockableFiles
 
39
from bzrlib.pack import ContainerPushParser
41
40
from bzrlib.smart import client, vfs
42
 
from bzrlib.revision import ensure_null, NULL_REVISION
 
41
from bzrlib.symbol_versioning import (
 
42
    deprecated_method,
 
43
    zero_ninetyone,
 
44
    )
 
45
from bzrlib.revision import NULL_REVISION
43
46
from bzrlib.trace import mutter, note, warning
44
47
 
45
 
 
46
 
class _RpcHelper(object):
47
 
    """Mixin class that helps with issuing RPCs."""
48
 
 
49
 
    def _call(self, method, *args, **err_context):
50
 
        try:
51
 
            return self._client.call(method, *args)
52
 
        except errors.ErrorFromSmartServer, err:
53
 
            self._translate_error(err, **err_context)
54
 
        
55
 
    def _call_expecting_body(self, method, *args, **err_context):
56
 
        try:
57
 
            return self._client.call_expecting_body(method, *args)
58
 
        except errors.ErrorFromSmartServer, err:
59
 
            self._translate_error(err, **err_context)
60
 
        
61
 
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
62
 
                                             **err_context):
63
 
        try:
64
 
            return self._client.call_with_body_bytes_expecting_body(
65
 
                method, args, body_bytes)
66
 
        except errors.ErrorFromSmartServer, err:
67
 
            self._translate_error(err, **err_context)
68
 
        
69
48
# Note: RemoteBzrDirFormat is in bzrdir.py
70
49
 
71
 
class RemoteBzrDir(BzrDir, _RpcHelper):
 
50
class RemoteBzrDir(BzrDir):
72
51
    """Control directory on a remote server, accessed via bzr:// or similar."""
73
52
 
74
53
    def __init__(self, transport, _client=None):
83
62
        self._real_bzrdir = None
84
63
 
85
64
        if _client is None:
86
 
            medium = transport.get_smart_medium()
87
 
            self._client = client._SmartClient(medium)
 
65
            self._shared_medium = transport.get_shared_medium()
 
66
            self._client = client._SmartClient(self._shared_medium)
88
67
        else:
89
68
            self._client = _client
 
69
            self._shared_medium = None
90
70
            return
91
71
 
92
72
        path = self._path_for_remote_call(self._client)
93
 
        response = self._call('BzrDir.open', path)
 
73
        response = self._client.call('BzrDir.open', path)
94
74
        if response not in [('yes',), ('no',)]:
95
75
            raise errors.UnexpectedSmartServerResponse(response)
96
76
        if response == ('no',):
105
85
            self._real_bzrdir = BzrDir.open_from_transport(
106
86
                self.root_transport, _server_formats=False)
107
87
 
108
 
    def _translate_error(self, err, **context):
109
 
        _translate_error(err, bzrdir=self, **context)
110
 
 
111
 
    def cloning_metadir(self, stacked=False):
112
 
        self._ensure_real()
113
 
        return self._real_bzrdir.cloning_metadir(stacked)
114
 
 
115
88
    def create_repository(self, shared=False):
116
89
        self._ensure_real()
117
90
        self._real_bzrdir.create_repository(shared=shared)
146
119
    def get_branch_reference(self):
147
120
        """See BzrDir.get_branch_reference()."""
148
121
        path = self._path_for_remote_call(self._client)
149
 
        response = self._call('BzrDir.open_branch', path)
 
122
        response = self._client.call('BzrDir.open_branch', path)
150
123
        if response[0] == 'ok':
151
124
            if response[1] == '':
152
125
                # branch at this location.
154
127
            else:
155
128
                # a branch reference, use the existing BranchReference logic.
156
129
                return response[1]
 
130
        elif response == ('nobranch',):
 
131
            raise errors.NotBranchError(path=self.root_transport.base)
157
132
        else:
158
133
            raise errors.UnexpectedSmartServerResponse(response)
159
134
 
162
137
        return None, self.open_branch()
163
138
 
164
139
    def open_branch(self, _unsupported=False):
165
 
        if _unsupported:
166
 
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
140
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
167
141
        reference_url = self.get_branch_reference()
168
142
        if reference_url is None:
169
143
            # branch at this location.
176
150
    def open_repository(self):
177
151
        path = self._path_for_remote_call(self._client)
178
152
        verb = 'BzrDir.find_repositoryV2'
179
 
        try:
180
 
            response = self._call(verb, path)
181
 
        except errors.UnknownSmartMethod:
 
153
        response = self._client.call(verb, path)
 
154
        if (response == ('error', "Generic bzr smart protocol error: "
 
155
                "bad request '%s'" % verb) or
 
156
              response == ('error', "Generic bzr smart protocol error: "
 
157
                "bad request u'%s'" % verb)):
182
158
            verb = 'BzrDir.find_repository'
183
 
            response = self._call(verb, path)
184
 
        if response[0] != 'ok':
185
 
            raise errors.UnexpectedSmartServerResponse(response)
 
159
            response = self._client.call(verb, path)
 
160
        assert response[0] in ('ok', 'norepository'), \
 
161
            'unexpected response code %s' % (response,)
 
162
        if response[0] == 'norepository':
 
163
            raise errors.NoRepositoryPresent(self)
186
164
        if verb == 'BzrDir.find_repository':
187
165
            # servers that don't support the V2 method don't support external
188
166
            # references either.
189
167
            response = response + ('no', )
190
 
        if not (len(response) == 5):
191
 
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
168
        assert len(response) == 5, 'incorrect response length %s' % (response,)
192
169
        if response[1] == '':
193
170
            format = RemoteRepositoryFormat()
194
171
            format.rich_root_data = (response[2] == 'yes')
195
172
            format.supports_tree_reference = (response[3] == 'yes')
196
173
            # No wire format to check this yet.
197
174
            format.supports_external_lookups = (response[4] == 'yes')
198
 
            # Used to support creating a real format instance when needed.
199
 
            format._creating_bzrdir = self
200
175
            return RemoteRepository(self, format)
201
176
        else:
202
177
            raise errors.NoRepositoryPresent(self)
232
207
        """Upgrading of remote bzrdirs is not supported yet."""
233
208
        return False
234
209
 
235
 
    def clone(self, url, revision_id=None, force_new_repo=False,
236
 
              preserve_stacking=False):
 
210
    def clone(self, url, revision_id=None, force_new_repo=False):
237
211
        self._ensure_real()
238
212
        return self._real_bzrdir.clone(url, revision_id=revision_id,
239
 
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
240
 
 
241
 
    def get_config(self):
242
 
        self._ensure_real()
243
 
        return self._real_bzrdir.get_config()
 
213
            force_new_repo=force_new_repo)
244
214
 
245
215
 
246
216
class RemoteRepositoryFormat(repository.RepositoryFormat):
256
226
    the class level.
257
227
    """
258
228
 
259
 
    _matchingbzrdir = RemoteBzrDirFormat()
 
229
    _matchingbzrdir = RemoteBzrDirFormat
260
230
 
261
231
    def initialize(self, a_bzrdir, shared=False):
262
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
263
 
            prior_repo = self._creating_bzrdir.open_repository()
264
 
            prior_repo._ensure_real()
265
 
            return prior_repo._real_repository._format.initialize(
266
 
                a_bzrdir, shared=shared)
 
232
        assert isinstance(a_bzrdir, RemoteBzrDir), \
 
233
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
267
234
        return a_bzrdir.create_repository(shared=shared)
268
235
    
269
236
    def open(self, a_bzrdir):
270
 
        if not isinstance(a_bzrdir, RemoteBzrDir):
271
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
237
        assert isinstance(a_bzrdir, RemoteBzrDir)
272
238
        return a_bzrdir.open_repository()
273
239
 
274
240
    def get_format_description(self):
287
253
                'Does not support nested trees', target_format)
288
254
 
289
255
 
290
 
class RemoteRepository(_RpcHelper):
 
256
class RemoteRepository(object):
291
257
    """Repository accessed over rpc.
292
258
 
293
259
    For the moment most operations are performed using local transport-backed
311
277
            self._real_repository = None
312
278
        self.bzrdir = remote_bzrdir
313
279
        if _client is None:
314
 
            self._client = remote_bzrdir._client
 
280
            self._client = client._SmartClient(self.bzrdir._shared_medium)
315
281
        else:
316
282
            self._client = _client
317
283
        self._format = format
319
285
        self._lock_token = None
320
286
        self._lock_count = 0
321
287
        self._leave_lock = False
322
 
        debug_cache = ('hpss' in debug.debug_flags)
323
 
        self._unstacked_provider = graph.CachingParentsProvider(
324
 
            get_parent_map=self._get_parent_map_rpc, debug=debug_cache)
325
 
        self._unstacked_provider.disable_cache()
 
288
        # A cache of looked up revision parent data; reset at unlock time.
 
289
        self._parents_map = None
 
290
        if 'hpss' in debug.debug_flags:
 
291
            self._requested_parents = None
326
292
        # For tests:
327
293
        # These depend on the actual remote format, so force them off for
328
294
        # maximum compatibility. XXX: In future these should depend on the
332
298
        self._reconcile_fixes_text_parents = False
333
299
        self._reconcile_backsup_inventory = False
334
300
        self.base = self.bzrdir.transport.base
335
 
        # Additional places to query for data.
336
 
        self._fallback_repositories = []
337
301
 
338
302
    def __str__(self):
339
303
        return "%s(%s)" % (self.__class__.__name__, self.base)
340
304
 
341
305
    __repr__ = __str__
342
306
 
343
 
    def abort_write_group(self, suppress_errors=False):
 
307
    def abort_write_group(self):
344
308
        """Complete a write group on the decorated repository.
345
309
        
346
310
        Smart methods peform operations in a single step so this api
347
311
        is not really applicable except as a compatibility thunk
348
312
        for older plugins that don't use e.g. the CommitBuilder
349
313
        facility.
350
 
 
351
 
        :param suppress_errors: see Repository.abort_write_group.
352
314
        """
353
315
        self._ensure_real()
354
 
        return self._real_repository.abort_write_group(
355
 
            suppress_errors=suppress_errors)
 
316
        return self._real_repository.abort_write_group()
356
317
 
357
318
    def commit_write_group(self):
358
319
        """Complete a write group on the decorated repository.
370
331
 
371
332
        Used before calls to self._real_repository.
372
333
        """
373
 
        if self._real_repository is None:
 
334
        if not self._real_repository:
374
335
            self.bzrdir._ensure_real()
375
 
            self._set_real_repository(
376
 
                self.bzrdir._real_bzrdir.open_repository())
377
 
 
378
 
    def _translate_error(self, err, **context):
379
 
        self.bzrdir._translate_error(err, repository=self, **context)
 
336
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
337
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
380
338
 
381
339
    def find_text_key_references(self):
382
340
        """Find the text key references within the repository.
403
361
        self._ensure_real()
404
362
        return self._real_repository._generate_text_key_index()
405
363
 
406
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
407
364
    def get_revision_graph(self, revision_id=None):
408
365
        """See Repository.get_revision_graph()."""
409
 
        return self._get_revision_graph(revision_id)
410
 
 
411
 
    def _get_revision_graph(self, revision_id):
412
 
        """Private method for using with old (< 1.2) servers to fallback."""
413
366
        if revision_id is None:
414
367
            revision_id = ''
415
368
        elif revision.is_null(revision_id):
416
369
            return {}
417
370
 
418
371
        path = self.bzrdir._path_for_remote_call(self._client)
419
 
        response = self._call_expecting_body(
 
372
        assert type(revision_id) is str
 
373
        response = self._client.call_expecting_body(
420
374
            'Repository.get_revision_graph', path, revision_id)
421
 
        response_tuple, response_handler = response
422
 
        if response_tuple[0] != 'ok':
423
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
424
 
        coded = response_handler.read_body_bytes()
425
 
        if coded == '':
426
 
            # no revisions in this repository!
427
 
            return {}
428
 
        lines = coded.split('\n')
429
 
        revision_graph = {}
430
 
        for line in lines:
431
 
            d = tuple(line.split())
432
 
            revision_graph[d[0]] = d[1:]
433
 
            
434
 
        return revision_graph
 
375
        if response[0][0] not in ['ok', 'nosuchrevision']:
 
376
            raise errors.UnexpectedSmartServerResponse(response[0])
 
377
        if response[0][0] == 'ok':
 
378
            coded = response[1].read_body_bytes()
 
379
            if coded == '':
 
380
                # no revisions in this repository!
 
381
                return {}
 
382
            lines = coded.split('\n')
 
383
            revision_graph = {}
 
384
            for line in lines:
 
385
                d = tuple(line.split())
 
386
                revision_graph[d[0]] = d[1:]
 
387
                
 
388
            return revision_graph
 
389
        else:
 
390
            response_body = response[1].read_body_bytes()
 
391
            assert response_body == ''
 
392
            raise NoSuchRevision(self, revision_id)
435
393
 
436
394
    def has_revision(self, revision_id):
437
395
        """See Repository.has_revision()."""
439
397
            # The null revision is always present.
440
398
            return True
441
399
        path = self.bzrdir._path_for_remote_call(self._client)
442
 
        response = self._call('Repository.has_revision', path, revision_id)
443
 
        if response[0] not in ('yes', 'no'):
444
 
            raise errors.UnexpectedSmartServerResponse(response)
445
 
        if response[0] == 'yes':
446
 
            return True
447
 
        for fallback_repo in self._fallback_repositories:
448
 
            if fallback_repo.has_revision(revision_id):
449
 
                return True
450
 
        return False
 
400
        response = self._client.call('Repository.has_revision', path, revision_id)
 
401
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
402
        return response[0] == 'yes'
451
403
 
452
404
    def has_revisions(self, revision_ids):
453
405
        """See Repository.has_revisions()."""
454
 
        # FIXME: This does many roundtrips, particularly when there are
455
 
        # fallback repositories.  -- mbp 20080905
456
406
        result = set()
457
407
        for revision_id in revision_ids:
458
408
            if self.has_revision(revision_id):
462
412
    def has_same_location(self, other):
463
413
        return (self.__class__ == other.__class__ and
464
414
                self.bzrdir.transport.base == other.bzrdir.transport.base)
465
 
 
 
415
        
466
416
    def get_graph(self, other_repository=None):
467
417
        """Return the graph for this repository format"""
468
 
        parents_provider = self._make_parents_provider(other_repository)
 
418
        parents_provider = self
 
419
        if (other_repository is not None and
 
420
            other_repository.bzrdir.transport.base !=
 
421
            self.bzrdir.transport.base):
 
422
            parents_provider = graph._StackedParentsProvider(
 
423
                [parents_provider, other_repository._make_parents_provider()])
469
424
        return graph.Graph(parents_provider)
470
425
 
471
426
    def gather_stats(self, revid=None, committers=None):
480
435
            fmt_committers = 'no'
481
436
        else:
482
437
            fmt_committers = 'yes'
483
 
        response_tuple, response_handler = self._call_expecting_body(
 
438
        response = self._client.call_expecting_body(
484
439
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
485
 
        if response_tuple[0] != 'ok':
486
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
440
        assert response[0][0] == 'ok', \
 
441
            'unexpected response code %s' % (response[0],)
487
442
 
488
 
        body = response_handler.read_body_bytes()
 
443
        body = response[1].read_body_bytes()
489
444
        result = {}
490
445
        for line in body.split('\n'):
491
446
            if not line:
525
480
    def is_shared(self):
526
481
        """See Repository.is_shared()."""
527
482
        path = self.bzrdir._path_for_remote_call(self._client)
528
 
        response = self._call('Repository.is_shared', path)
529
 
        if response[0] not in ('yes', 'no'):
530
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
483
        response = self._client.call('Repository.is_shared', path)
 
484
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
531
485
        return response[0] == 'yes'
532
486
 
533
487
    def is_write_locked(self):
538
492
        if not self._lock_mode:
539
493
            self._lock_mode = 'r'
540
494
            self._lock_count = 1
541
 
            self._unstacked_provider.enable_cache(cache_misses=False)
 
495
            self._parents_map = {}
 
496
            if 'hpss' in debug.debug_flags:
 
497
                self._requested_parents = set()
542
498
            if self._real_repository is not None:
543
499
                self._real_repository.lock_read()
544
500
        else:
548
504
        path = self.bzrdir._path_for_remote_call(self._client)
549
505
        if token is None:
550
506
            token = ''
551
 
        err_context = {'token': token}
552
 
        response = self._call('Repository.lock_write', path, token,
553
 
                              **err_context)
 
507
        response = self._client.call('Repository.lock_write', path, token)
554
508
        if response[0] == 'ok':
555
509
            ok, token = response
556
510
            return token
 
511
        elif response[0] == 'LockContention':
 
512
            raise errors.LockContention('(remote lock)')
 
513
        elif response[0] == 'UnlockableTransport':
 
514
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
515
        elif response[0] == 'LockFailed':
 
516
            raise errors.LockFailed(response[1], response[2])
557
517
        else:
558
518
            raise errors.UnexpectedSmartServerResponse(response)
559
519
 
560
 
    def lock_write(self, token=None, _skip_rpc=False):
 
520
    def lock_write(self, token=None):
561
521
        if not self._lock_mode:
562
 
            if _skip_rpc:
563
 
                if self._lock_token is not None:
564
 
                    if token != self._lock_token:
565
 
                        raise errors.TokenMismatch(token, self._lock_token)
566
 
                self._lock_token = token
567
 
            else:
568
 
                self._lock_token = self._remote_lock_write(token)
 
522
            self._lock_token = self._remote_lock_write(token)
569
523
            # if self._lock_token is None, then this is something like packs or
570
524
            # svn where we don't get to lock the repo, or a weave style repository
571
525
            # where we cannot lock it over the wire and attempts to do so will
578
532
                self._leave_lock = False
579
533
            self._lock_mode = 'w'
580
534
            self._lock_count = 1
581
 
            self._unstacked_provider.enable_cache(cache_misses=False)
 
535
            self._parents_map = {}
 
536
            if 'hpss' in debug.debug_flags:
 
537
                self._requested_parents = set()
582
538
        elif self._lock_mode == 'r':
583
539
            raise errors.ReadOnlyError(self)
584
540
        else:
601
557
        :param repository: The repository to fallback to for non-hpss
602
558
            implemented operations.
603
559
        """
604
 
        if self._real_repository is not None:
605
 
            raise AssertionError('_real_repository is already set')
606
 
        if isinstance(repository, RemoteRepository):
607
 
            raise AssertionError()
 
560
        assert not isinstance(repository, RemoteRepository)
608
561
        self._real_repository = repository
609
 
        for fb in self._fallback_repositories:
610
 
            self._real_repository.add_fallback_repository(fb)
611
562
        if self._lock_mode == 'w':
612
563
            # if we are already locked, the real repository must be able to
613
564
            # acquire the lock with our token.
631
582
        if not token:
632
583
            # with no token the remote repository is not persistently locked.
633
584
            return
634
 
        err_context = {'token': token}
635
 
        response = self._call('Repository.unlock', path, token,
636
 
                              **err_context)
 
585
        response = self._client.call('Repository.unlock', path, token)
637
586
        if response == ('ok',):
638
587
            return
 
588
        elif response[0] == 'TokenMismatch':
 
589
            raise errors.TokenMismatch(token, '(remote token)')
639
590
        else:
640
591
            raise errors.UnexpectedSmartServerResponse(response)
641
592
 
643
594
        self._lock_count -= 1
644
595
        if self._lock_count > 0:
645
596
            return
646
 
        self._unstacked_provider.disable_cache()
 
597
        self._parents_map = None
 
598
        if 'hpss' in debug.debug_flags:
 
599
            self._requested_parents = None
647
600
        old_mode = self._lock_mode
648
601
        self._lock_mode = None
649
602
        try:
677
630
        """
678
631
        import tempfile
679
632
        path = self.bzrdir._path_for_remote_call(self._client)
680
 
        try:
681
 
            response, protocol = self._call_expecting_body(
682
 
                'Repository.tarball', path, compression)
683
 
        except errors.UnknownSmartMethod:
684
 
            protocol.cancel_read_body()
685
 
            return None
 
633
        response, protocol = self._client.call_expecting_body(
 
634
            'Repository.tarball', path, compression)
686
635
        if response[0] == 'ok':
687
636
            # Extract the tarball and return it
688
637
            t = tempfile.NamedTemporaryFile()
690
639
            t.write(protocol.read_body_bytes())
691
640
            t.seek(0)
692
641
            return t
 
642
        if (response == ('error', "Generic bzr smart protocol error: "
 
643
                "bad request 'Repository.tarball'") or
 
644
              response == ('error', "Generic bzr smart protocol error: "
 
645
                "bad request u'Repository.tarball'")):
 
646
            protocol.cancel_read_body()
 
647
            return None
693
648
        raise errors.UnexpectedSmartServerResponse(response)
694
649
 
695
650
    def sprout(self, to_bzrdir, revision_id=None):
716
671
        # FIXME: It ought to be possible to call this without immediately
717
672
        # triggering _ensure_real.  For now it's the easiest thing to do.
718
673
        self._ensure_real()
719
 
        real_repo = self._real_repository
720
 
        builder = real_repo.get_commit_builder(branch, parents,
 
674
        builder = self._real_repository.get_commit_builder(branch, parents,
721
675
                config, timestamp=timestamp, timezone=timezone,
722
676
                committer=committer, revprops=revprops, revision_id=revision_id)
723
677
        return builder
724
678
 
725
 
    def add_fallback_repository(self, repository):
726
 
        """Add a repository to use for looking up data not held locally.
727
 
        
728
 
        :param repository: A repository.
729
 
        """
730
 
        # XXX: At the moment the RemoteRepository will allow fallbacks
731
 
        # unconditionally - however, a _real_repository will usually exist,
732
 
        # and may raise an error if it's not accommodated by the underlying
733
 
        # format.  Eventually we should check when opening the repository
734
 
        # whether it's willing to allow them or not.
735
 
        #
736
 
        # We need to accumulate additional repositories here, to pass them in
737
 
        # on various RPC's.
738
 
        self._fallback_repositories.append(repository)
739
 
        # They are also seen by the fallback repository.  If it doesn't exist
740
 
        # yet they'll be added then.  This implicitly copies them.
741
 
        self._ensure_real()
742
 
 
743
679
    def add_inventory(self, revid, inv, parents):
744
680
        self._ensure_real()
745
681
        return self._real_repository.add_inventory(revid, inv, parents)
763
699
        self._ensure_real()
764
700
        return self._real_repository.get_revision(revision_id)
765
701
 
 
702
    @property
 
703
    def weave_store(self):
 
704
        self._ensure_real()
 
705
        return self._real_repository.weave_store
 
706
 
766
707
    def get_transaction(self):
767
708
        self._ensure_real()
768
709
        return self._real_repository.get_transaction()
773
714
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
774
715
 
775
716
    def make_working_trees(self):
776
 
        """See Repository.make_working_trees"""
777
 
        self._ensure_real()
778
 
        return self._real_repository.make_working_trees()
 
717
        """RemoteRepositories never create working trees by default."""
 
718
        return False
779
719
 
780
720
    def revision_ids_to_search_result(self, result_set):
781
721
        """Convert a set of revision ids to a graph SearchResult."""
801
741
        return repository.InterRepository.get(
802
742
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
803
743
 
804
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
805
 
        # Not delegated to _real_repository so that InterRepository.get has a
806
 
        # chance to find an InterRepository specialised for RemoteRepository.
 
744
    def fetch(self, source, revision_id=None, pb=None):
807
745
        if self.has_same_location(source):
808
746
            # check that last_revision is in 'from' and then return a
809
747
            # no-operation.
811
749
                not revision.is_null(revision_id)):
812
750
                self.get_revision(revision_id)
813
751
            return 0, []
814
 
        inter = repository.InterRepository.get(source, self)
815
 
        try:
816
 
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
817
 
        except NotImplementedError:
818
 
            raise errors.IncompatibleRepositories(source, self)
 
752
        self._ensure_real()
 
753
        return self._real_repository.fetch(
 
754
            source, revision_id=revision_id, pb=pb)
819
755
 
820
756
    def create_bundle(self, target, base, fileobj, format=None):
821
757
        self._ensure_real()
822
758
        self._real_repository.create_bundle(target, base, fileobj, format)
823
759
 
 
760
    @property
 
761
    def control_weaves(self):
 
762
        self._ensure_real()
 
763
        return self._real_repository.control_weaves
 
764
 
824
765
    @needs_read_lock
825
766
    def get_ancestry(self, revision_id, topo_sorted=True):
826
767
        self._ensure_real()
827
768
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
828
769
 
 
770
    @needs_read_lock
 
771
    def get_inventory_weave(self):
 
772
        self._ensure_real()
 
773
        return self._real_repository.get_inventory_weave()
 
774
 
829
775
    def fileids_altered_by_revision_ids(self, revision_ids):
830
776
        self._ensure_real()
831
777
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
841
787
        self._ensure_real()
842
788
        return self._real_repository.iter_files_bytes(desired_files)
843
789
 
844
 
    @property
845
 
    def _fetch_order(self):
846
 
        """Decorate the real repository for now.
847
 
 
848
 
        In the long term getting this back from the remote repository as part
849
 
        of open would be more efficient.
850
 
        """
851
 
        self._ensure_real()
852
 
        return self._real_repository._fetch_order
853
 
 
854
 
    @property
855
 
    def _fetch_uses_deltas(self):
856
 
        """Decorate the real repository for now.
857
 
 
858
 
        In the long term getting this back from the remote repository as part
859
 
        of open would be more efficient.
860
 
        """
861
 
        self._ensure_real()
862
 
        return self._real_repository._fetch_uses_deltas
863
 
 
864
 
    @property
865
 
    def _fetch_reconcile(self):
866
 
        """Decorate the real repository for now.
867
 
 
868
 
        In the long term getting this back from the remote repository as part
869
 
        of open would be more efficient.
870
 
        """
871
 
        self._ensure_real()
872
 
        return self._real_repository._fetch_reconcile
873
 
 
874
 
    def get_parent_map(self, revision_ids):
 
790
    def get_parent_map(self, keys):
875
791
        """See bzrlib.Graph.get_parent_map()."""
876
 
        return self._make_parents_provider().get_parent_map(revision_ids)
877
 
 
878
 
    def _get_parent_map_rpc(self, keys):
 
792
        # Hack to build up the caching logic.
 
793
        ancestry = self._parents_map
 
794
        if ancestry is None:
 
795
            # Repository is not locked, so there's no cache.
 
796
            missing_revisions = set(keys)
 
797
            ancestry = {}
 
798
        else:
 
799
            missing_revisions = set(key for key in keys if key not in ancestry)
 
800
        if missing_revisions:
 
801
            parent_map = self._get_parent_map(missing_revisions)
 
802
            if 'hpss' in debug.debug_flags:
 
803
                mutter('retransmitted revisions: %d of %d',
 
804
                        len(set(ancestry).intersection(parent_map)),
 
805
                        len(parent_map))
 
806
            ancestry.update(parent_map)
 
807
        present_keys = [k for k in keys if k in ancestry]
 
808
        if 'hpss' in debug.debug_flags:
 
809
            self._requested_parents.update(present_keys)
 
810
            mutter('Current RemoteRepository graph hit rate: %d%%',
 
811
                100.0 * len(self._requested_parents) / len(ancestry))
 
812
        return dict((k, ancestry[k]) for k in present_keys)
 
813
 
 
814
    def _response_is_unknown_method(self, response, verb):
 
815
        """Return True if response is an unknonwn method response to verb.
 
816
        
 
817
        :param response: The response from a smart client call_expecting_body
 
818
            call.
 
819
        :param verb: The verb used in that call.
 
820
        :return: True if an unknown method was encountered.
 
821
        """
 
822
        # This might live better on
 
823
        # bzrlib.smart.protocol.SmartClientRequestProtocolOne
 
824
        if (response[0] == ('error', "Generic bzr smart protocol error: "
 
825
                "bad request '%s'" % verb) or
 
826
              response[0] == ('error', "Generic bzr smart protocol error: "
 
827
                "bad request u'%s'" % verb)):
 
828
           response[1].cancel_read_body()
 
829
           return True
 
830
        return False
 
831
 
 
832
    def _get_parent_map(self, keys):
879
833
        """Helper for get_parent_map that performs the RPC."""
880
 
        medium = self._client._medium
881
 
        if medium._is_remote_before((1, 2)):
 
834
        medium = self._client.get_smart_medium()
 
835
        if not medium._remote_is_at_least_1_2:
882
836
            # We already found out that the server can't understand
883
837
            # Repository.get_parent_map requests, so just fetch the whole
884
838
            # graph.
885
 
            # XXX: Note that this will issue a deprecation warning. This is ok
886
 
            # :- its because we're working with a deprecated server anyway, and
887
 
            # the user will almost certainly have seen a warning about the
888
 
            # server version already.
889
 
            rg = self.get_revision_graph()
890
 
            # There is an api discrepency between get_parent_map and
891
 
            # get_revision_graph. Specifically, a "key:()" pair in
892
 
            # get_revision_graph just means a node has no parents. For
893
 
            # "get_parent_map" it means the node is a ghost. So fix up the
894
 
            # graph to correct this.
895
 
            #   https://bugs.launchpad.net/bzr/+bug/214894
896
 
            # There is one other "bug" which is that ghosts in
897
 
            # get_revision_graph() are not returned at all. But we won't worry
898
 
            # about that for now.
899
 
            for node_id, parent_ids in rg.iteritems():
900
 
                if parent_ids == ():
901
 
                    rg[node_id] = (NULL_REVISION,)
902
 
            rg[NULL_REVISION] = ()
903
 
            return rg
 
839
            return self.get_revision_graph()
904
840
 
905
841
        keys = set(keys)
906
 
        if None in keys:
907
 
            raise ValueError('get_parent_map(None) is not valid')
908
842
        if NULL_REVISION in keys:
909
843
            keys.discard(NULL_REVISION)
910
844
            found_parents = {NULL_REVISION:()}
923
857
        # TODO: Manage this incrementally to avoid covering the same path
924
858
        # repeatedly. (The server will have to on each request, but the less
925
859
        # work done the better).
926
 
        parents_map = self._unstacked_provider.get_cached_map()
 
860
        parents_map = self._parents_map
927
861
        if parents_map is None:
928
862
            # Repository is not locked, so there's no cache.
929
863
            parents_map = {}
938
872
        body = self._serialise_search_recipe(recipe)
939
873
        path = self.bzrdir._path_for_remote_call(self._client)
940
874
        for key in keys:
941
 
            if type(key) is not str:
942
 
                raise ValueError(
943
 
                    "key %r not a plain string" % (key,))
 
875
            assert type(key) is str
944
876
        verb = 'Repository.get_parent_map'
945
877
        args = (path,) + tuple(keys)
946
 
        try:
947
 
            response = self._call_with_body_bytes_expecting_body(
948
 
                verb, args, body)
949
 
        except errors.UnknownSmartMethod:
 
878
        response = self._client.call_with_body_bytes_expecting_body(
 
879
            verb, args, self._serialise_search_recipe(recipe))
 
880
        if self._response_is_unknown_method(response, verb):
950
881
            # Server does not support this method, so get the whole graph.
951
882
            # Worse, we have to force a disconnection, because the server now
952
883
            # doesn't realise it has a body on the wire to consume, so the
957
888
            medium.disconnect()
958
889
            # To avoid having to disconnect repeatedly, we keep track of the
959
890
            # fact the server doesn't understand remote methods added in 1.2.
960
 
            medium._remember_remote_is_before((1, 2))
961
 
            return self.get_revision_graph(None)
962
 
        response_tuple, response_handler = response
963
 
        if response_tuple[0] not in ['ok']:
964
 
            response_handler.cancel_read_body()
965
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
966
 
        if response_tuple[0] == 'ok':
967
 
            coded = bz2.decompress(response_handler.read_body_bytes())
 
891
            medium._remote_is_at_least_1_2 = False
 
892
            return self.get_revision_graph()
 
893
        elif response[0][0] not in ['ok']:
 
894
            reponse[1].cancel_read_body()
 
895
            raise errors.UnexpectedSmartServerResponse(response[0])
 
896
        if response[0][0] == 'ok':
 
897
            coded = bz2.decompress(response[1].read_body_bytes())
968
898
            if coded == '':
969
899
                # no revisions found
970
900
                return {}
1043
973
        # destination
1044
974
        from bzrlib import osutils
1045
975
        import tarfile
 
976
        import tempfile
1046
977
        # TODO: Maybe a progress bar while streaming the tarball?
1047
978
        note("Copying repository content as tarball...")
1048
979
        tar_file = self._get_tarball('bz2')
1052
983
        try:
1053
984
            tar = tarfile.open('repository', fileobj=tar_file,
1054
985
                mode='r|bz2')
1055
 
            tmpdir = osutils.mkdtemp()
 
986
            tmpdir = tempfile.mkdtemp()
1056
987
            try:
1057
988
                _extract_tar(tar, tmpdir)
1058
989
                tmp_bzrdir = BzrDir.open(tmpdir)
1066
997
        # TODO: Suggestion from john: using external tar is much faster than
1067
998
        # python's tarfile library, but it may not work on windows.
1068
999
 
1069
 
    @property
1070
 
    def inventories(self):
1071
 
        """Decorate the real repository for now.
1072
 
 
1073
 
        In the long term a full blown network facility is needed to
1074
 
        avoid creating a real repository object locally.
1075
 
        """
1076
 
        self._ensure_real()
1077
 
        return self._real_repository.inventories
1078
 
 
1079
1000
    @needs_write_lock
1080
1001
    def pack(self):
1081
1002
        """Compress the data within the repository.
1085
1006
        self._ensure_real()
1086
1007
        return self._real_repository.pack()
1087
1008
 
1088
 
    @property
1089
 
    def revisions(self):
1090
 
        """Decorate the real repository for now.
1091
 
 
1092
 
        In the short term this should become a real object to intercept graph
1093
 
        lookups.
1094
 
 
1095
 
        In the long term a full blown network facility is needed.
1096
 
        """
1097
 
        self._ensure_real()
1098
 
        return self._real_repository.revisions
1099
 
 
1100
1009
    def set_make_working_trees(self, new_value):
1101
 
        self._ensure_real()
1102
 
        self._real_repository.set_make_working_trees(new_value)
1103
 
 
1104
 
    @property
1105
 
    def signatures(self):
1106
 
        """Decorate the real repository for now.
1107
 
 
1108
 
        In the long term a full blown network facility is needed to avoid
1109
 
        creating a real repository object locally.
1110
 
        """
1111
 
        self._ensure_real()
1112
 
        return self._real_repository.signatures
 
1010
        raise NotImplementedError(self.set_make_working_trees)
1113
1011
 
1114
1012
    @needs_write_lock
1115
1013
    def sign_revision(self, revision_id, gpg_strategy):
1116
1014
        self._ensure_real()
1117
1015
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
1118
1016
 
1119
 
    @property
1120
 
    def texts(self):
1121
 
        """Decorate the real repository for now.
1122
 
 
1123
 
        In the long term a full blown network facility is needed to avoid
1124
 
        creating a real repository object locally.
1125
 
        """
1126
 
        self._ensure_real()
1127
 
        return self._real_repository.texts
1128
 
 
1129
1017
    @needs_read_lock
1130
1018
    def get_revisions(self, revision_ids):
1131
1019
        self._ensure_real()
1157
1045
        self._ensure_real()
1158
1046
        return self._real_repository.has_signature_for_revision_id(revision_id)
1159
1047
 
 
1048
    def get_data_stream_for_search(self, search):
 
1049
        medium = self._client.get_smart_medium()
 
1050
        if not medium._remote_is_at_least_1_2:
 
1051
            self._ensure_real()
 
1052
            return self._real_repository.get_data_stream_for_search(search)
 
1053
        REQUEST_NAME = 'Repository.stream_revisions_chunked'
 
1054
        path = self.bzrdir._path_for_remote_call(self._client)
 
1055
        body = self._serialise_search_recipe(search.get_recipe())
 
1056
        response, protocol = self._client.call_with_body_bytes_expecting_body(
 
1057
            REQUEST_NAME, (path,), body)
 
1058
 
 
1059
        if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
 
1060
            # Server does not support this method, so fall back to VFS.
 
1061
            # Worse, we have to force a disconnection, because the server now
 
1062
            # doesn't realise it has a body on the wire to consume, so the
 
1063
            # only way to recover is to abandon the connection.
 
1064
            warning(
 
1065
                'Server is too old for streaming pull, reconnecting.  '
 
1066
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
1067
            medium.disconnect()
 
1068
            # To avoid having to disconnect repeatedly, we keep track of the
 
1069
            # fact the server doesn't understand this remote method.
 
1070
            medium._remote_is_at_least_1_2 = False
 
1071
            self._ensure_real()
 
1072
            return self._real_repository.get_data_stream_for_search(search)
 
1073
 
 
1074
        if response == ('ok',):
 
1075
            return self._deserialise_stream(protocol)
 
1076
        if response == ('NoSuchRevision', ):
 
1077
            # We cannot easily identify the revision that is missing in this
 
1078
            # situation without doing much more network IO. For now, bail.
 
1079
            raise NoSuchRevision(self, "unknown")
 
1080
        else:
 
1081
            raise errors.UnexpectedSmartServerResponse(response)
 
1082
 
 
1083
    def _deserialise_stream(self, protocol):
 
1084
        stream = protocol.read_streamed_body()
 
1085
        container_parser = ContainerPushParser()
 
1086
        for bytes in stream:
 
1087
            container_parser.accept_bytes(bytes)
 
1088
            records = container_parser.read_pending_records()
 
1089
            for record_names, record_bytes in records:
 
1090
                if len(record_names) != 1:
 
1091
                    # These records should have only one name, and that name
 
1092
                    # should be a one-element tuple.
 
1093
                    raise errors.SmartProtocolError(
 
1094
                        'Repository data stream had invalid record name %r'
 
1095
                        % (record_names,))
 
1096
                name_tuple = record_names[0]
 
1097
                yield name_tuple, record_bytes
 
1098
 
 
1099
    def insert_data_stream(self, stream):
 
1100
        self._ensure_real()
 
1101
        self._real_repository.insert_data_stream(stream)
 
1102
 
1160
1103
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1161
1104
        self._ensure_real()
1162
1105
        return self._real_repository.item_keys_introduced_by(revision_ids,
1175
1118
        self._ensure_real()
1176
1119
        return self._real_repository._check_for_inconsistent_revision_parents()
1177
1120
 
1178
 
    def _make_parents_provider(self, other=None):
1179
 
        providers = [self._unstacked_provider]
1180
 
        if other is not None:
1181
 
            providers.insert(0, other)
1182
 
        providers.extend(r._make_parents_provider() for r in
1183
 
                         self._fallback_repositories)
1184
 
        return graph._StackedParentsProvider(providers)
 
1121
    def _make_parents_provider(self):
 
1122
        return self
1185
1123
 
1186
1124
    def _serialise_search_recipe(self, recipe):
1187
1125
        """Serialise a graph search recipe.
1194
1132
        count = str(recipe[2])
1195
1133
        return '\n'.join((start_keys, stop_keys, count))
1196
1134
 
1197
 
    def autopack(self):
1198
 
        path = self.bzrdir._path_for_remote_call(self._client)
1199
 
        try:
1200
 
            response = self._call('PackRepository.autopack', path)
1201
 
        except errors.UnknownSmartMethod:
1202
 
            self._ensure_real()
1203
 
            self._real_repository._pack_collection.autopack()
1204
 
            return
1205
 
        if self._real_repository is not None:
1206
 
            # Reset the real repository's cache of pack names.
1207
 
            # XXX: At some point we may be able to skip this and just rely on
1208
 
            # the automatic retry logic to do the right thing, but for now we
1209
 
            # err on the side of being correct rather than being optimal.
1210
 
            self._real_repository._pack_collection.reload_pack_names()
1211
 
        if response[0] != 'ok':
1212
 
            raise errors.UnexpectedSmartServerResponse(response)
1213
 
 
1214
1135
 
1215
1136
class RemoteBranchLockableFiles(LockableFiles):
1216
1137
    """A 'LockableFiles' implementation that talks to a smart server.
1231
1152
        self._dir_mode = None
1232
1153
        self._file_mode = None
1233
1154
 
 
1155
    def get(self, path):
 
1156
        """'get' a remote path as per the LockableFiles interface.
 
1157
 
 
1158
        :param path: the file to 'get'. If this is 'branch.conf', we do not
 
1159
             just retrieve a file, instead we ask the smart server to generate
 
1160
             a configuration for us - which is retrieved as an INI file.
 
1161
        """
 
1162
        if path == 'branch.conf':
 
1163
            path = self.bzrdir._path_for_remote_call(self._client)
 
1164
            response = self._client.call_expecting_body(
 
1165
                'Branch.get_config_file', path)
 
1166
            assert response[0][0] == 'ok', \
 
1167
                'unexpected response code %s' % (response[0],)
 
1168
            return StringIO(response[1].read_body_bytes())
 
1169
        else:
 
1170
            # VFS fallback.
 
1171
            return LockableFiles.get(self, path)
 
1172
 
1234
1173
 
1235
1174
class RemoteBranchFormat(branch.BranchFormat):
1236
1175
 
1237
 
    def __init__(self):
1238
 
        super(RemoteBranchFormat, self).__init__()
1239
 
        self._matchingbzrdir = RemoteBzrDirFormat()
1240
 
        self._matchingbzrdir.set_branch_format(self)
1241
 
 
1242
1176
    def __eq__(self, other):
1243
1177
        return (isinstance(other, RemoteBranchFormat) and 
1244
1178
            self.__dict__ == other.__dict__)
1250
1184
        return 'Remote BZR Branch'
1251
1185
 
1252
1186
    def open(self, a_bzrdir):
 
1187
        assert isinstance(a_bzrdir, RemoteBzrDir)
1253
1188
        return a_bzrdir.open_branch()
1254
1189
 
1255
1190
    def initialize(self, a_bzrdir):
 
1191
        assert isinstance(a_bzrdir, RemoteBzrDir)
1256
1192
        return a_bzrdir.create_branch()
1257
1193
 
1258
1194
    def supports_tags(self):
1261
1197
        return True
1262
1198
 
1263
1199
 
1264
 
class RemoteBranch(branch.Branch, _RpcHelper):
 
1200
class RemoteBranch(branch.Branch):
1265
1201
    """Branch stored on a server accessed by HPSS RPC.
1266
1202
 
1267
1203
    At the moment most operations are mapped down to simple file operations.
1280
1216
        # And the parent's __init__ doesn't do much anyway.
1281
1217
        self._revision_id_to_revno_cache = None
1282
1218
        self._revision_history_cache = None
1283
 
        self._last_revision_info_cache = None
1284
1219
        self.bzrdir = remote_bzrdir
1285
1220
        if _client is not None:
1286
1221
            self._client = _client
1287
1222
        else:
1288
 
            self._client = remote_bzrdir._client
 
1223
            self._client = client._SmartClient(self.bzrdir._shared_medium)
1289
1224
        self.repository = remote_repository
1290
1225
        if real_branch is not None:
1291
1226
            self._real_branch = real_branch
1305
1240
        self._control_files = None
1306
1241
        self._lock_mode = None
1307
1242
        self._lock_token = None
1308
 
        self._repo_lock_token = None
1309
1243
        self._lock_count = 0
1310
1244
        self._leave_lock = False
1311
 
        # The base class init is not called, so we duplicate this:
1312
 
        hooks = branch.Branch.hooks['open']
1313
 
        for hook in hooks:
1314
 
            hook(self)
1315
 
        self._setup_stacking()
1316
 
 
1317
 
    def _setup_stacking(self):
1318
 
        # configure stacking into the remote repository, by reading it from
1319
 
        # the vfs branch.
1320
 
        try:
1321
 
            fallback_url = self.get_stacked_on_url()
1322
 
        except (errors.NotStacked, errors.UnstackableBranchFormat,
1323
 
            errors.UnstackableRepositoryFormat), e:
1324
 
            return
1325
 
        # it's relative to this branch...
1326
 
        fallback_url = urlutils.join(self.base, fallback_url)
1327
 
        transports = [self.bzrdir.root_transport]
1328
 
        if self._real_branch is not None:
1329
 
            transports.append(self._real_branch._transport)
1330
 
        stacked_on = branch.Branch.open(fallback_url,
1331
 
                                        possible_transports=transports)
1332
 
        self.repository.add_fallback_repository(stacked_on.repository)
1333
 
 
1334
 
    def _get_real_transport(self):
1335
 
        # if we try vfs access, return the real branch's vfs transport
1336
 
        self._ensure_real()
1337
 
        return self._real_branch._transport
1338
 
 
1339
 
    _transport = property(_get_real_transport)
1340
1245
 
1341
1246
    def __str__(self):
1342
1247
        return "%s(%s)" % (self.__class__.__name__, self.base)
1348
1253
 
1349
1254
        Used before calls to self._real_branch.
1350
1255
        """
1351
 
        if self._real_branch is None:
1352
 
            if not vfs.vfs_enabled():
1353
 
                raise AssertionError('smart server vfs must be enabled '
1354
 
                    'to use vfs implementation')
 
1256
        if not self._real_branch:
 
1257
            assert vfs.vfs_enabled()
1355
1258
            self.bzrdir._ensure_real()
1356
1259
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1357
 
            if self.repository._real_repository is None:
1358
 
                # Give the remote repository the matching real repo.
1359
 
                real_repo = self._real_branch.repository
1360
 
                if isinstance(real_repo, RemoteRepository):
1361
 
                    real_repo._ensure_real()
1362
 
                    real_repo = real_repo._real_repository
1363
 
                self.repository._set_real_repository(real_repo)
1364
 
            # Give the real branch the remote repository to let fast-pathing
1365
 
            # happen.
 
1260
            # Give the remote repository the matching real repo.
 
1261
            real_repo = self._real_branch.repository
 
1262
            if isinstance(real_repo, RemoteRepository):
 
1263
                real_repo._ensure_real()
 
1264
                real_repo = real_repo._real_repository
 
1265
            self.repository._set_real_repository(real_repo)
 
1266
            # Give the branch the remote repository to let fast-pathing happen.
1366
1267
            self._real_branch.repository = self.repository
 
1268
            # XXX: deal with _lock_mode == 'w'
1367
1269
            if self._lock_mode == 'r':
1368
1270
                self._real_branch.lock_read()
1369
 
            elif self._lock_mode == 'w':
1370
 
                self._real_branch.lock_write(token=self._lock_token)
1371
 
 
1372
 
    def _translate_error(self, err, **context):
1373
 
        self.repository._translate_error(err, branch=self, **context)
1374
 
 
1375
 
    def _clear_cached_state(self):
1376
 
        super(RemoteBranch, self)._clear_cached_state()
1377
 
        if self._real_branch is not None:
1378
 
            self._real_branch._clear_cached_state()
1379
 
 
1380
 
    def _clear_cached_state_of_remote_branch_only(self):
1381
 
        """Like _clear_cached_state, but doesn't clear the cache of
1382
 
        self._real_branch.
1383
 
 
1384
 
        This is useful when falling back to calling a method of
1385
 
        self._real_branch that changes state.  In that case the underlying
1386
 
        branch changes, so we need to invalidate this RemoteBranch's cache of
1387
 
        it.  However, there's no need to invalidate the _real_branch's cache
1388
 
        too, in fact doing so might harm performance.
1389
 
        """
1390
 
        super(RemoteBranch, self)._clear_cached_state()
1391
 
        
 
1271
 
1392
1272
    @property
1393
1273
    def control_files(self):
1394
1274
        # Defer actually creating RemoteBranchLockableFiles until its needed,
1408
1288
        self._ensure_real()
1409
1289
        return self._real_branch.get_physical_lock_status()
1410
1290
 
1411
 
    def get_stacked_on_url(self):
1412
 
        """Get the URL this branch is stacked against.
1413
 
 
1414
 
        :raises NotStacked: If the branch is not stacked.
1415
 
        :raises UnstackableBranchFormat: If the branch does not support
1416
 
            stacking.
1417
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1418
 
            stacking.
1419
 
        """
1420
 
        try:
1421
 
            # there may not be a repository yet, so we can't use
1422
 
            # self._translate_error, so we can't use self._call either.
1423
 
            response = self._client.call('Branch.get_stacked_on_url',
1424
 
                self._remote_path())
1425
 
        except errors.ErrorFromSmartServer, err:
1426
 
            # there may not be a repository yet, so we can't call through
1427
 
            # its _translate_error
1428
 
            _translate_error(err, branch=self)
1429
 
        except errors.UnknownSmartMethod, err:
1430
 
            self._ensure_real()
1431
 
            return self._real_branch.get_stacked_on_url()
1432
 
        if response[0] != 'ok':
1433
 
            raise errors.UnexpectedSmartServerResponse(response)
1434
 
        return response[1]
1435
 
 
1436
1291
    def lock_read(self):
1437
 
        self.repository.lock_read()
1438
1292
        if not self._lock_mode:
1439
1293
            self._lock_mode = 'r'
1440
1294
            self._lock_count = 1
1450
1304
            branch_token = token
1451
1305
            repo_token = self.repository.lock_write()
1452
1306
            self.repository.unlock()
1453
 
        err_context = {'token': token}
1454
 
        response = self._call(
1455
 
            'Branch.lock_write', self._remote_path(), branch_token,
1456
 
            repo_token or '', **err_context)
1457
 
        if response[0] != 'ok':
 
1307
        path = self.bzrdir._path_for_remote_call(self._client)
 
1308
        response = self._client.call('Branch.lock_write', path, branch_token,
 
1309
                                     repo_token or '')
 
1310
        if response[0] == 'ok':
 
1311
            ok, branch_token, repo_token = response
 
1312
            return branch_token, repo_token
 
1313
        elif response[0] == 'LockContention':
 
1314
            raise errors.LockContention('(remote lock)')
 
1315
        elif response[0] == 'TokenMismatch':
 
1316
            raise errors.TokenMismatch(token, '(remote token)')
 
1317
        elif response[0] == 'UnlockableTransport':
 
1318
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
1319
        elif response[0] == 'ReadOnlyError':
 
1320
            raise errors.ReadOnlyError(self)
 
1321
        elif response[0] == 'LockFailed':
 
1322
            raise errors.LockFailed(response[1], response[2])
 
1323
        else:
1458
1324
            raise errors.UnexpectedSmartServerResponse(response)
1459
 
        ok, branch_token, repo_token = response
1460
 
        return branch_token, repo_token
1461
1325
            
1462
1326
    def lock_write(self, token=None):
1463
1327
        if not self._lock_mode:
1464
 
            # Lock the branch and repo in one remote call.
1465
1328
            remote_tokens = self._remote_lock_write(token)
1466
1329
            self._lock_token, self._repo_lock_token = remote_tokens
1467
 
            if not self._lock_token:
1468
 
                raise SmartProtocolError('Remote server did not return a token!')
1469
 
            # Tell the self.repository object that it is locked.
1470
 
            self.repository.lock_write(
1471
 
                self._repo_lock_token, _skip_rpc=True)
1472
 
 
 
1330
            assert self._lock_token, 'Remote server did not return a token!'
 
1331
            # TODO: We really, really, really don't want to call _ensure_real
 
1332
            # here, but it's the easiest way to ensure coherency between the
 
1333
            # state of the RemoteBranch and RemoteRepository objects and the
 
1334
            # physical locks.  If we don't materialise the real objects here,
 
1335
            # then getting everything in the right state later is complex, so
 
1336
            # for now we just do it the lazy way.
 
1337
            #   -- Andrew Bennetts, 2007-02-22.
 
1338
            self._ensure_real()
1473
1339
            if self._real_branch is not None:
1474
 
                self._real_branch.lock_write(token=self._lock_token)
 
1340
                self._real_branch.repository.lock_write(
 
1341
                    token=self._repo_lock_token)
 
1342
                try:
 
1343
                    self._real_branch.lock_write(token=self._lock_token)
 
1344
                finally:
 
1345
                    self._real_branch.repository.unlock()
1475
1346
            if token is not None:
1476
1347
                self._leave_lock = True
1477
1348
            else:
 
1349
                # XXX: this case seems to be unreachable; token cannot be None.
1478
1350
                self._leave_lock = False
1479
1351
            self._lock_mode = 'w'
1480
1352
            self._lock_count = 1
1482
1354
            raise errors.ReadOnlyTransaction
1483
1355
        else:
1484
1356
            if token is not None:
1485
 
                # A token was given to lock_write, and we're relocking, so
1486
 
                # check that the given token actually matches the one we
1487
 
                # already have.
 
1357
                # A token was given to lock_write, and we're relocking, so check
 
1358
                # that the given token actually matches the one we already have.
1488
1359
                if token != self._lock_token:
1489
1360
                    raise errors.TokenMismatch(token, self._lock_token)
1490
1361
            self._lock_count += 1
1491
 
            # Re-lock the repository too.
1492
 
            self.repository.lock_write(self._repo_lock_token)
1493
1362
        return self._lock_token or None
1494
1363
 
1495
1364
    def _unlock(self, branch_token, repo_token):
1496
 
        err_context = {'token': str((branch_token, repo_token))}
1497
 
        response = self._call(
1498
 
            'Branch.unlock', self._remote_path(), branch_token,
1499
 
            repo_token or '', **err_context)
 
1365
        path = self.bzrdir._path_for_remote_call(self._client)
 
1366
        response = self._client.call('Branch.unlock', path, branch_token,
 
1367
                                     repo_token or '')
1500
1368
        if response == ('ok',):
1501
1369
            return
1502
 
        raise errors.UnexpectedSmartServerResponse(response)
 
1370
        elif response[0] == 'TokenMismatch':
 
1371
            raise errors.TokenMismatch(
 
1372
                str((branch_token, repo_token)), '(remote tokens)')
 
1373
        else:
 
1374
            raise errors.UnexpectedSmartServerResponse(response)
1503
1375
 
1504
1376
    def unlock(self):
1505
 
        try:
1506
 
            self._lock_count -= 1
1507
 
            if not self._lock_count:
1508
 
                self._clear_cached_state()
1509
 
                mode = self._lock_mode
1510
 
                self._lock_mode = None
1511
 
                if self._real_branch is not None:
1512
 
                    if (not self._leave_lock and mode == 'w' and
1513
 
                        self._repo_lock_token):
1514
 
                        # If this RemoteBranch will remove the physical lock
1515
 
                        # for the repository, make sure the _real_branch
1516
 
                        # doesn't do it first.  (Because the _real_branch's
1517
 
                        # repository is set to be the RemoteRepository.)
1518
 
                        self._real_branch.repository.leave_lock_in_place()
1519
 
                    self._real_branch.unlock()
1520
 
                if mode != 'w':
1521
 
                    # Only write-locked branched need to make a remote method
1522
 
                    # call to perfom the unlock.
1523
 
                    return
1524
 
                if not self._lock_token:
1525
 
                    raise AssertionError('Locked, but no token!')
1526
 
                branch_token = self._lock_token
1527
 
                repo_token = self._repo_lock_token
1528
 
                self._lock_token = None
1529
 
                self._repo_lock_token = None
1530
 
                if not self._leave_lock:
1531
 
                    self._unlock(branch_token, repo_token)
1532
 
        finally:
1533
 
            self.repository.unlock()
 
1377
        self._lock_count -= 1
 
1378
        if not self._lock_count:
 
1379
            self._clear_cached_state()
 
1380
            mode = self._lock_mode
 
1381
            self._lock_mode = None
 
1382
            if self._real_branch is not None:
 
1383
                if (not self._leave_lock and mode == 'w' and
 
1384
                    self._repo_lock_token):
 
1385
                    # If this RemoteBranch will remove the physical lock for the
 
1386
                    # repository, make sure the _real_branch doesn't do it
 
1387
                    # first.  (Because the _real_branch's repository is set to
 
1388
                    # be the RemoteRepository.)
 
1389
                    self._real_branch.repository.leave_lock_in_place()
 
1390
                self._real_branch.unlock()
 
1391
            if mode != 'w':
 
1392
                # Only write-locked branched need to make a remote method call
 
1393
                # to perfom the unlock.
 
1394
                return
 
1395
            assert self._lock_token, 'Locked, but no token!'
 
1396
            branch_token = self._lock_token
 
1397
            repo_token = self._repo_lock_token
 
1398
            self._lock_token = None
 
1399
            self._repo_lock_token = None
 
1400
            if not self._leave_lock:
 
1401
                self._unlock(branch_token, repo_token)
1534
1402
 
1535
1403
    def break_lock(self):
1536
1404
        self._ensure_real()
1546
1414
            raise NotImplementedError(self.dont_leave_lock_in_place)
1547
1415
        self._leave_lock = False
1548
1416
 
1549
 
    def _last_revision_info(self):
1550
 
        response = self._call('Branch.last_revision_info', self._remote_path())
1551
 
        if response[0] != 'ok':
1552
 
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1417
    def last_revision_info(self):
 
1418
        """See Branch.last_revision_info()."""
 
1419
        path = self.bzrdir._path_for_remote_call(self._client)
 
1420
        response = self._client.call('Branch.last_revision_info', path)
 
1421
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1553
1422
        revno = int(response[1])
1554
1423
        last_revision = response[2]
1555
1424
        return (revno, last_revision)
1556
1425
 
1557
1426
    def _gen_revision_history(self):
1558
1427
        """See Branch._gen_revision_history()."""
1559
 
        response_tuple, response_handler = self._call_expecting_body(
1560
 
            'Branch.revision_history', self._remote_path())
1561
 
        if response_tuple[0] != 'ok':
1562
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1563
 
        result = response_handler.read_body_bytes().split('\x00')
 
1428
        path = self.bzrdir._path_for_remote_call(self._client)
 
1429
        response = self._client.call_expecting_body(
 
1430
            'Branch.revision_history', path)
 
1431
        assert response[0][0] == 'ok', ('unexpected response code %s'
 
1432
                                        % (response[0],))
 
1433
        result = response[1].read_body_bytes().split('\x00')
1564
1434
        if result == ['']:
1565
1435
            return []
1566
1436
        return result
1567
1437
 
1568
 
    def _remote_path(self):
1569
 
        return self.bzrdir._path_for_remote_call(self._client)
1570
 
 
1571
 
    def _set_last_revision_descendant(self, revision_id, other_branch,
1572
 
            allow_diverged=False, allow_overwrite_descendant=False):
1573
 
        err_context = {'other_branch': other_branch}
1574
 
        response = self._call('Branch.set_last_revision_ex',
1575
 
            self._remote_path(), self._lock_token, self._repo_lock_token,
1576
 
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1577
 
            **err_context)
1578
 
        self._clear_cached_state()
1579
 
        if len(response) != 3 and response[0] != 'ok':
1580
 
            raise errors.UnexpectedSmartServerResponse(response)
1581
 
        new_revno, new_revision_id = response[1:]
1582
 
        self._last_revision_info_cache = new_revno, new_revision_id
1583
 
        if self._real_branch is not None:
1584
 
            cache = new_revno, new_revision_id
1585
 
            self._real_branch._last_revision_info_cache = cache
1586
 
 
1587
 
    def _set_last_revision(self, revision_id):
1588
 
        self._clear_cached_state()
1589
 
        response = self._call('Branch.set_last_revision',
1590
 
            self._remote_path(), self._lock_token, self._repo_lock_token,
1591
 
            revision_id)
1592
 
        if response != ('ok',):
1593
 
            raise errors.UnexpectedSmartServerResponse(response)
1594
 
 
1595
1438
    @needs_write_lock
1596
1439
    def set_revision_history(self, rev_history):
1597
1440
        # Send just the tip revision of the history; the server will generate
1598
1441
        # the full history from that.  If the revision doesn't exist in this
1599
1442
        # branch, NoSuchRevision will be raised.
 
1443
        path = self.bzrdir._path_for_remote_call(self._client)
1600
1444
        if rev_history == []:
1601
1445
            rev_id = 'null:'
1602
1446
        else:
1603
1447
            rev_id = rev_history[-1]
1604
 
        self._set_last_revision(rev_id)
 
1448
        self._clear_cached_state()
 
1449
        response = self._client.call('Branch.set_last_revision',
 
1450
            path, self._lock_token, self._repo_lock_token, rev_id)
 
1451
        if response[0] == 'NoSuchRevision':
 
1452
            raise NoSuchRevision(self, rev_id)
 
1453
        else:
 
1454
            assert response == ('ok',), (
 
1455
                'unexpected response code %r' % (response,))
1605
1456
        self._cache_revision_history(rev_history)
1606
1457
 
1607
1458
    def get_parent(self):
1612
1463
        self._ensure_real()
1613
1464
        return self._real_branch.set_parent(url)
1614
1465
        
1615
 
    def set_stacked_on_url(self, stacked_location):
1616
 
        """Set the URL this branch is stacked against.
 
1466
    def get_config(self):
 
1467
        return RemoteBranchConfig(self)
1617
1468
 
1618
 
        :raises UnstackableBranchFormat: If the branch does not support
1619
 
            stacking.
1620
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1621
 
            stacking.
1622
 
        """
 
1469
    def sprout(self, to_bzrdir, revision_id=None):
 
1470
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1471
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1472
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1473
        # to_bzrdir.create_branch...
1623
1474
        self._ensure_real()
1624
 
        return self._real_branch.set_stacked_on_url(stacked_location)
1625
 
 
1626
 
    def sprout(self, to_bzrdir, revision_id=None):
1627
 
        branch_format = to_bzrdir._format._branch_format
1628
 
        if (branch_format is None or
1629
 
            isinstance(branch_format, RemoteBranchFormat)):
1630
 
            # The to_bzrdir specifies RemoteBranchFormat (or no format, which
1631
 
            # implies the same thing), but RemoteBranches can't be created at
1632
 
            # arbitrary URLs.  So create a branch in the same format as
1633
 
            # _real_branch instead.
1634
 
            # XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1635
 
            # to_bzrdir.create_branch to create a RemoteBranch after all...
1636
 
            self._ensure_real()
1637
 
            result = self._real_branch._format.initialize(to_bzrdir)
1638
 
            self.copy_content_into(result, revision_id=revision_id)
1639
 
            result.set_parent(self.bzrdir.root_transport.base)
1640
 
        else:
1641
 
            result = branch.Branch.sprout(
1642
 
                self, to_bzrdir, revision_id=revision_id)
 
1475
        result = self._real_branch._format.initialize(to_bzrdir)
 
1476
        self.copy_content_into(result, revision_id=revision_id)
 
1477
        result.set_parent(self.bzrdir.root_transport.base)
1643
1478
        return result
1644
1479
 
1645
1480
    @needs_write_lock
1646
1481
    def pull(self, source, overwrite=False, stop_revision=None,
1647
1482
             **kwargs):
1648
 
        self._clear_cached_state_of_remote_branch_only()
 
1483
        # FIXME: This asks the real branch to run the hooks, which means
 
1484
        # they're called with the wrong target branch parameter. 
 
1485
        # The test suite specifically allows this at present but it should be
 
1486
        # fixed.  It should get a _override_hook_target branch,
 
1487
        # as push does.  -- mbp 20070405
1649
1488
        self._ensure_real()
1650
 
        return self._real_branch.pull(
 
1489
        self._real_branch.pull(
1651
1490
            source, overwrite=overwrite, stop_revision=stop_revision,
1652
 
            _override_hook_target=self, **kwargs)
 
1491
            **kwargs)
1653
1492
 
1654
1493
    @needs_read_lock
1655
1494
    def push(self, target, overwrite=False, stop_revision=None):
1661
1500
    def is_locked(self):
1662
1501
        return self._lock_count >= 1
1663
1502
 
1664
 
    @needs_read_lock
1665
 
    def revision_id_to_revno(self, revision_id):
1666
 
        self._ensure_real()
1667
 
        return self._real_branch.revision_id_to_revno(revision_id)
1668
 
 
1669
 
    @needs_write_lock
1670
1503
    def set_last_revision_info(self, revno, revision_id):
1671
 
        revision_id = ensure_null(revision_id)
1672
 
        try:
1673
 
            response = self._call('Branch.set_last_revision_info',
1674
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
1675
 
                str(revno), revision_id)
1676
 
        except errors.UnknownSmartMethod:
1677
 
            self._ensure_real()
1678
 
            self._clear_cached_state_of_remote_branch_only()
1679
 
            self._real_branch.set_last_revision_info(revno, revision_id)
1680
 
            self._last_revision_info_cache = revno, revision_id
1681
 
            return
1682
 
        if response == ('ok',):
1683
 
            self._clear_cached_state()
1684
 
            self._last_revision_info_cache = revno, revision_id
1685
 
            # Update the _real_branch's cache too.
1686
 
            if self._real_branch is not None:
1687
 
                cache = self._last_revision_info_cache
1688
 
                self._real_branch._last_revision_info_cache = cache
1689
 
        else:
1690
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1504
        self._ensure_real()
 
1505
        self._clear_cached_state()
 
1506
        return self._real_branch.set_last_revision_info(revno, revision_id)
1691
1507
 
1692
 
    @needs_write_lock
1693
1508
    def generate_revision_history(self, revision_id, last_rev=None,
1694
1509
                                  other_branch=None):
1695
 
        medium = self._client._medium
1696
 
        if not medium._is_remote_before((1, 6)):
1697
 
            try:
1698
 
                self._set_last_revision_descendant(revision_id, other_branch,
1699
 
                    allow_diverged=True, allow_overwrite_descendant=True)
1700
 
                return
1701
 
            except errors.UnknownSmartMethod:
1702
 
                medium._remember_remote_is_before((1, 6))
1703
 
        self._clear_cached_state_of_remote_branch_only()
1704
1510
        self._ensure_real()
1705
 
        self._real_branch.generate_revision_history(
 
1511
        return self._real_branch.generate_revision_history(
1706
1512
            revision_id, last_rev=last_rev, other_branch=other_branch)
1707
1513
 
1708
1514
    @property
1714
1520
        self._ensure_real()
1715
1521
        return self._real_branch.set_push_location(location)
1716
1522
 
1717
 
    @needs_write_lock
1718
 
    def update_revisions(self, other, stop_revision=None, overwrite=False,
1719
 
                         graph=None):
1720
 
        """See Branch.update_revisions."""
1721
 
        other.lock_read()
1722
 
        try:
1723
 
            if stop_revision is None:
1724
 
                stop_revision = other.last_revision()
1725
 
                if revision.is_null(stop_revision):
1726
 
                    # if there are no commits, we're done.
1727
 
                    return
1728
 
            self.fetch(other, stop_revision)
1729
 
 
1730
 
            if overwrite:
1731
 
                # Just unconditionally set the new revision.  We don't care if
1732
 
                # the branches have diverged.
1733
 
                self._set_last_revision(stop_revision)
1734
 
            else:
1735
 
                medium = self._client._medium
1736
 
                if not medium._is_remote_before((1, 6)):
1737
 
                    try:
1738
 
                        self._set_last_revision_descendant(stop_revision, other)
1739
 
                        return
1740
 
                    except errors.UnknownSmartMethod:
1741
 
                        medium._remember_remote_is_before((1, 6))
1742
 
                # Fallback for pre-1.6 servers: check for divergence
1743
 
                # client-side, then do _set_last_revision.
1744
 
                last_rev = revision.ensure_null(self.last_revision())
1745
 
                if graph is None:
1746
 
                    graph = self.repository.get_graph()
1747
 
                if self._check_if_descendant_or_diverged(
1748
 
                        stop_revision, last_rev, graph, other):
1749
 
                    # stop_revision is a descendant of last_rev, but we aren't
1750
 
                    # overwriting, so we're done.
1751
 
                    return
1752
 
                self._set_last_revision(stop_revision)
1753
 
        finally:
1754
 
            other.unlock()
 
1523
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1524
        self._ensure_real()
 
1525
        return self._real_branch.update_revisions(
 
1526
            other, stop_revision=stop_revision, overwrite=overwrite)
 
1527
 
 
1528
 
 
1529
class RemoteBranchConfig(BranchConfig):
 
1530
 
 
1531
    def username(self):
 
1532
        self.branch._ensure_real()
 
1533
        return self.branch._real_branch.get_config().username()
 
1534
 
 
1535
    def _get_branch_data_config(self):
 
1536
        self.branch._ensure_real()
 
1537
        if self._branch_data_config is None:
 
1538
            self._branch_data_config = TreeConfig(self.branch._real_branch)
 
1539
        return self._branch_data_config
1755
1540
 
1756
1541
 
1757
1542
def _extract_tar(tar, to_dir):
1761
1546
    """
1762
1547
    for tarinfo in tar:
1763
1548
        tar.extract(tarinfo, to_dir)
1764
 
 
1765
 
 
1766
 
def _translate_error(err, **context):
1767
 
    """Translate an ErrorFromSmartServer into a more useful error.
1768
 
 
1769
 
    Possible context keys:
1770
 
      - branch
1771
 
      - repository
1772
 
      - bzrdir
1773
 
      - token
1774
 
      - other_branch
1775
 
      - path
1776
 
 
1777
 
    If the error from the server doesn't match a known pattern, then
1778
 
    UnknownErrorFromSmartServer is raised.
1779
 
    """
1780
 
    def find(name):
1781
 
        try:
1782
 
            return context[name]
1783
 
        except KeyError, key_err:
1784
 
            mutter('Missing key %r in context %r', key_err.args[0], context)
1785
 
            raise err
1786
 
    def get_path():
1787
 
        """Get the path from the context if present, otherwise use first error
1788
 
        arg.
1789
 
        """
1790
 
        try:
1791
 
            return context['path']
1792
 
        except KeyError, key_err:
1793
 
            try:
1794
 
                return err.error_args[0]
1795
 
            except IndexError, idx_err:
1796
 
                mutter(
1797
 
                    'Missing key %r in context %r', key_err.args[0], context)
1798
 
                raise err
1799
 
 
1800
 
    if err.error_verb == 'NoSuchRevision':
1801
 
        raise NoSuchRevision(find('branch'), err.error_args[0])
1802
 
    elif err.error_verb == 'nosuchrevision':
1803
 
        raise NoSuchRevision(find('repository'), err.error_args[0])
1804
 
    elif err.error_tuple == ('nobranch',):
1805
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1806
 
    elif err.error_verb == 'norepository':
1807
 
        raise errors.NoRepositoryPresent(find('bzrdir'))
1808
 
    elif err.error_verb == 'LockContention':
1809
 
        raise errors.LockContention('(remote lock)')
1810
 
    elif err.error_verb == 'UnlockableTransport':
1811
 
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
1812
 
    elif err.error_verb == 'LockFailed':
1813
 
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
1814
 
    elif err.error_verb == 'TokenMismatch':
1815
 
        raise errors.TokenMismatch(find('token'), '(remote token)')
1816
 
    elif err.error_verb == 'Diverged':
1817
 
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
1818
 
    elif err.error_verb == 'TipChangeRejected':
1819
 
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1820
 
    elif err.error_verb == 'UnstackableBranchFormat':
1821
 
        raise errors.UnstackableBranchFormat(*err.error_args)
1822
 
    elif err.error_verb == 'UnstackableRepositoryFormat':
1823
 
        raise errors.UnstackableRepositoryFormat(*err.error_args)
1824
 
    elif err.error_verb == 'NotStacked':
1825
 
        raise errors.NotStacked(branch=find('branch'))
1826
 
    elif err.error_verb == 'PermissionDenied':
1827
 
        path = get_path()
1828
 
        if len(err.error_args) >= 2:
1829
 
            extra = err.error_args[1]
1830
 
        else:
1831
 
            extra = None
1832
 
        raise errors.PermissionDenied(path, extra=extra)
1833
 
    elif err.error_verb == 'ReadError':
1834
 
        path = get_path()
1835
 
        raise errors.ReadError(path)
1836
 
    elif err.error_verb == 'NoSuchFile':
1837
 
        path = get_path()
1838
 
        raise errors.NoSuchFile(path)
1839
 
    elif err.error_verb == 'FileExists':
1840
 
        raise errors.FileExists(err.error_args[0])
1841
 
    elif err.error_verb == 'DirectoryNotEmpty':
1842
 
        raise errors.DirectoryNotEmpty(err.error_args[0])
1843
 
    elif err.error_verb == 'ShortReadvError':
1844
 
        args = err.error_args
1845
 
        raise errors.ShortReadvError(
1846
 
            args[0], int(args[1]), int(args[2]), int(args[3]))
1847
 
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1848
 
        encoding = str(err.error_args[0]) # encoding must always be a string
1849
 
        val = err.error_args[1]
1850
 
        start = int(err.error_args[2])
1851
 
        end = int(err.error_args[3])
1852
 
        reason = str(err.error_args[4]) # reason must always be a string
1853
 
        if val.startswith('u:'):
1854
 
            val = val[2:].decode('utf-8')
1855
 
        elif val.startswith('s:'):
1856
 
            val = val[2:].decode('base64')
1857
 
        if err.error_verb == 'UnicodeDecodeError':
1858
 
            raise UnicodeDecodeError(encoding, val, start, end, reason)
1859
 
        elif err.error_verb == 'UnicodeEncodeError':
1860
 
            raise UnicodeEncodeError(encoding, val, start, end, reason)
1861
 
    elif err.error_verb == 'ReadOnlyError':
1862
 
        raise errors.TransportNotPossible('readonly transport')
1863
 
    raise errors.UnknownErrorFromSmartServer(err)