~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: John Arbash Meinel
  • Date: 2008-07-11 21:41:24 UTC
  • mto: This revision was merged to the branch mainline in revision 3543.
  • Revision ID: john@arbash-meinel.com-20080711214124-qi09irlj7pd5cuzg
Shortcut the case when one revision is in the ancestry of the other.

At the cost of a heads() check, when one parent supersedes, we don't have to extract
the text for the other. Changes merge time from 3m37s => 3m21s. Using a
CachingParentsProvider would drop the time down to 3m11s.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
37
from bzrlib.errors import (
37
38
    NoSuchRevision,
38
39
    SmartProtocolError,
39
40
    )
40
41
from bzrlib.lockable_files import LockableFiles
 
42
from bzrlib.pack import ContainerPushParser
41
43
from bzrlib.smart import client, vfs
 
44
from bzrlib.symbol_versioning import (
 
45
    deprecated_in,
 
46
    deprecated_method,
 
47
    )
42
48
from bzrlib.revision import ensure_null, NULL_REVISION
43
49
from bzrlib.trace import mutter, note, warning
44
50
 
45
51
 
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
52
# Note: RemoteBzrDirFormat is in bzrdir.py
70
53
 
71
 
class RemoteBzrDir(BzrDir, _RpcHelper):
 
54
class RemoteBzrDir(BzrDir):
72
55
    """Control directory on a remote server, accessed via bzr:// or similar."""
73
56
 
74
57
    def __init__(self, transport, _client=None):
90
73
            return
91
74
 
92
75
        path = self._path_for_remote_call(self._client)
93
 
        response = self._call('BzrDir.open', path)
 
76
        response = self._client.call('BzrDir.open', path)
94
77
        if response not in [('yes',), ('no',)]:
95
78
            raise errors.UnexpectedSmartServerResponse(response)
96
79
        if response == ('no',):
105
88
            self._real_bzrdir = BzrDir.open_from_transport(
106
89
                self.root_transport, _server_formats=False)
107
90
 
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
91
    def create_repository(self, shared=False):
116
92
        self._ensure_real()
117
93
        self._real_bzrdir.create_repository(shared=shared)
146
122
    def get_branch_reference(self):
147
123
        """See BzrDir.get_branch_reference()."""
148
124
        path = self._path_for_remote_call(self._client)
149
 
        response = self._call('BzrDir.open_branch', path)
 
125
        try:
 
126
            response = self._client.call('BzrDir.open_branch', path)
 
127
        except errors.ErrorFromSmartServer, err:
 
128
            if err.error_tuple == ('nobranch',):
 
129
                raise errors.NotBranchError(path=self.root_transport.base)
 
130
            raise
150
131
        if response[0] == 'ok':
151
132
            if response[1] == '':
152
133
                # branch at this location.
177
158
        path = self._path_for_remote_call(self._client)
178
159
        verb = 'BzrDir.find_repositoryV2'
179
160
        try:
180
 
            response = self._call(verb, path)
181
 
        except errors.UnknownSmartMethod:
182
 
            verb = 'BzrDir.find_repository'
183
 
            response = self._call(verb, path)
 
161
            try:
 
162
                response = self._client.call(verb, path)
 
163
            except errors.UnknownSmartMethod:
 
164
                verb = 'BzrDir.find_repository'
 
165
                response = self._client.call(verb, path)
 
166
        except errors.ErrorFromSmartServer, err:
 
167
            if err.error_verb == 'norepository':
 
168
                raise errors.NoRepositoryPresent(self)
 
169
            raise
184
170
        if response[0] != 'ok':
185
171
            raise errors.UnexpectedSmartServerResponse(response)
186
172
        if verb == 'BzrDir.find_repository':
195
181
            format.supports_tree_reference = (response[3] == 'yes')
196
182
            # No wire format to check this yet.
197
183
            format.supports_external_lookups = (response[4] == 'yes')
198
 
            # Used to support creating a real format instance when needed.
199
 
            format._creating_bzrdir = self
200
184
            return RemoteRepository(self, format)
201
185
        else:
202
186
            raise errors.NoRepositoryPresent(self)
230
214
 
231
215
    def needs_format_conversion(self, format=None):
232
216
        """Upgrading of remote bzrdirs is not supported yet."""
233
 
        if format is None:
234
 
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
235
 
                % 'needs_format_conversion(format=None)')
236
217
        return False
237
218
 
238
 
    def clone(self, url, revision_id=None, force_new_repo=False,
239
 
              preserve_stacking=False):
 
219
    def clone(self, url, revision_id=None, force_new_repo=False):
240
220
        self._ensure_real()
241
221
        return self._real_bzrdir.clone(url, revision_id=revision_id,
242
 
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
243
 
 
244
 
    def get_config(self):
245
 
        self._ensure_real()
246
 
        return self._real_bzrdir.get_config()
 
222
            force_new_repo=force_new_repo)
247
223
 
248
224
 
249
225
class RemoteRepositoryFormat(repository.RepositoryFormat):
259
235
    the class level.
260
236
    """
261
237
 
262
 
    _matchingbzrdir = RemoteBzrDirFormat()
 
238
    _matchingbzrdir = RemoteBzrDirFormat
263
239
 
264
240
    def initialize(self, a_bzrdir, shared=False):
265
241
        if not isinstance(a_bzrdir, RemoteBzrDir):
266
 
            prior_repo = self._creating_bzrdir.open_repository()
267
 
            prior_repo._ensure_real()
268
 
            return prior_repo._real_repository._format.initialize(
269
 
                a_bzrdir, shared=shared)
 
242
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
270
243
        return a_bzrdir.create_repository(shared=shared)
271
244
    
272
245
    def open(self, a_bzrdir):
290
263
                'Does not support nested trees', target_format)
291
264
 
292
265
 
293
 
class RemoteRepository(_RpcHelper):
 
266
class RemoteRepository(object):
294
267
    """Repository accessed over rpc.
295
268
 
296
269
    For the moment most operations are performed using local transport-backed
322
295
        self._lock_token = None
323
296
        self._lock_count = 0
324
297
        self._leave_lock = False
325
 
        self._unstacked_provider = graph.CachingParentsProvider(
326
 
            get_parent_map=self._get_parent_map_rpc)
327
 
        self._unstacked_provider.disable_cache()
 
298
        # A cache of looked up revision parent data; reset at unlock time.
 
299
        self._parents_map = None
 
300
        if 'hpss' in debug.debug_flags:
 
301
            self._requested_parents = None
328
302
        # For tests:
329
303
        # These depend on the actual remote format, so force them off for
330
304
        # maximum compatibility. XXX: In future these should depend on the
334
308
        self._reconcile_fixes_text_parents = False
335
309
        self._reconcile_backsup_inventory = False
336
310
        self.base = self.bzrdir.transport.base
337
 
        # Additional places to query for data.
338
 
        self._fallback_repositories = []
339
311
 
340
312
    def __str__(self):
341
313
        return "%s(%s)" % (self.__class__.__name__, self.base)
342
314
 
343
315
    __repr__ = __str__
344
316
 
345
 
    def abort_write_group(self, suppress_errors=False):
 
317
    def abort_write_group(self):
346
318
        """Complete a write group on the decorated repository.
347
319
        
348
320
        Smart methods peform operations in a single step so this api
349
321
        is not really applicable except as a compatibility thunk
350
322
        for older plugins that don't use e.g. the CommitBuilder
351
323
        facility.
352
 
 
353
 
        :param suppress_errors: see Repository.abort_write_group.
354
324
        """
355
325
        self._ensure_real()
356
 
        return self._real_repository.abort_write_group(
357
 
            suppress_errors=suppress_errors)
 
326
        return self._real_repository.abort_write_group()
358
327
 
359
328
    def commit_write_group(self):
360
329
        """Complete a write group on the decorated repository.
372
341
 
373
342
        Used before calls to self._real_repository.
374
343
        """
375
 
        if self._real_repository is None:
 
344
        if not self._real_repository:
376
345
            self.bzrdir._ensure_real()
377
 
            self._set_real_repository(
378
 
                self.bzrdir._real_bzrdir.open_repository())
379
 
 
380
 
    def _translate_error(self, err, **context):
381
 
        self.bzrdir._translate_error(err, repository=self, **context)
 
346
            #self._real_repository = self.bzrdir._real_bzrdir.open_repository()
 
347
            self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
382
348
 
383
349
    def find_text_key_references(self):
384
350
        """Find the text key references within the repository.
418
384
            return {}
419
385
 
420
386
        path = self.bzrdir._path_for_remote_call(self._client)
421
 
        response = self._call_expecting_body(
422
 
            'Repository.get_revision_graph', path, revision_id)
 
387
        try:
 
388
            response = self._client.call_expecting_body(
 
389
                'Repository.get_revision_graph', path, revision_id)
 
390
        except errors.ErrorFromSmartServer, err:
 
391
            if err.error_verb == 'nosuchrevision':
 
392
                raise NoSuchRevision(self, revision_id)
 
393
            raise
423
394
        response_tuple, response_handler = response
424
395
        if response_tuple[0] != 'ok':
425
396
            raise errors.UnexpectedSmartServerResponse(response_tuple)
441
412
            # The null revision is always present.
442
413
            return True
443
414
        path = self.bzrdir._path_for_remote_call(self._client)
444
 
        response = self._call('Repository.has_revision', path, revision_id)
 
415
        response = self._client.call(
 
416
            'Repository.has_revision', path, revision_id)
445
417
        if response[0] not in ('yes', 'no'):
446
418
            raise errors.UnexpectedSmartServerResponse(response)
447
 
        if response[0] == 'yes':
448
 
            return True
449
 
        for fallback_repo in self._fallback_repositories:
450
 
            if fallback_repo.has_revision(revision_id):
451
 
                return True
452
 
        return False
 
419
        return response[0] == 'yes'
453
420
 
454
421
    def has_revisions(self, revision_ids):
455
422
        """See Repository.has_revisions()."""
456
 
        # FIXME: This does many roundtrips, particularly when there are
457
 
        # fallback repositories.  -- mbp 20080905
458
423
        result = set()
459
424
        for revision_id in revision_ids:
460
425
            if self.has_revision(revision_id):
464
429
    def has_same_location(self, other):
465
430
        return (self.__class__ == other.__class__ and
466
431
                self.bzrdir.transport.base == other.bzrdir.transport.base)
467
 
 
 
432
        
468
433
    def get_graph(self, other_repository=None):
469
434
        """Return the graph for this repository format"""
470
 
        parents_provider = self._make_parents_provider(other_repository)
 
435
        parents_provider = self
 
436
        if (other_repository is not None and
 
437
            other_repository.bzrdir.transport.base !=
 
438
            self.bzrdir.transport.base):
 
439
            parents_provider = graph._StackedParentsProvider(
 
440
                [parents_provider, other_repository._make_parents_provider()])
471
441
        return graph.Graph(parents_provider)
472
442
 
473
443
    def gather_stats(self, revid=None, committers=None):
482
452
            fmt_committers = 'no'
483
453
        else:
484
454
            fmt_committers = 'yes'
485
 
        response_tuple, response_handler = self._call_expecting_body(
 
455
        response_tuple, response_handler = self._client.call_expecting_body(
486
456
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
487
457
        if response_tuple[0] != 'ok':
488
458
            raise errors.UnexpectedSmartServerResponse(response_tuple)
527
497
    def is_shared(self):
528
498
        """See Repository.is_shared()."""
529
499
        path = self.bzrdir._path_for_remote_call(self._client)
530
 
        response = self._call('Repository.is_shared', path)
 
500
        response = self._client.call('Repository.is_shared', path)
531
501
        if response[0] not in ('yes', 'no'):
532
502
            raise SmartProtocolError('unexpected response code %s' % (response,))
533
503
        return response[0] == 'yes'
540
510
        if not self._lock_mode:
541
511
            self._lock_mode = 'r'
542
512
            self._lock_count = 1
543
 
            self._unstacked_provider.enable_cache(cache_misses=False)
 
513
            self._parents_map = {}
 
514
            if 'hpss' in debug.debug_flags:
 
515
                self._requested_parents = set()
544
516
            if self._real_repository is not None:
545
517
                self._real_repository.lock_read()
546
518
        else:
550
522
        path = self.bzrdir._path_for_remote_call(self._client)
551
523
        if token is None:
552
524
            token = ''
553
 
        err_context = {'token': token}
554
 
        response = self._call('Repository.lock_write', path, token,
555
 
                              **err_context)
 
525
        try:
 
526
            response = self._client.call('Repository.lock_write', path, token)
 
527
        except errors.ErrorFromSmartServer, err:
 
528
            if err.error_verb == 'LockContention':
 
529
                raise errors.LockContention('(remote lock)')
 
530
            elif err.error_verb == 'UnlockableTransport':
 
531
                raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
532
            elif err.error_verb == 'LockFailed':
 
533
                raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
534
            raise
 
535
 
556
536
        if response[0] == 'ok':
557
537
            ok, token = response
558
538
            return token
559
539
        else:
560
540
            raise errors.UnexpectedSmartServerResponse(response)
561
541
 
562
 
    def lock_write(self, token=None, _skip_rpc=False):
 
542
    def lock_write(self, token=None):
563
543
        if not self._lock_mode:
564
 
            if _skip_rpc:
565
 
                if self._lock_token is not None:
566
 
                    if token != self._lock_token:
567
 
                        raise errors.TokenMismatch(token, self._lock_token)
568
 
                self._lock_token = token
569
 
            else:
570
 
                self._lock_token = self._remote_lock_write(token)
 
544
            self._lock_token = self._remote_lock_write(token)
571
545
            # if self._lock_token is None, then this is something like packs or
572
546
            # svn where we don't get to lock the repo, or a weave style repository
573
547
            # where we cannot lock it over the wire and attempts to do so will
580
554
                self._leave_lock = False
581
555
            self._lock_mode = 'w'
582
556
            self._lock_count = 1
583
 
            self._unstacked_provider.enable_cache(cache_misses=False)
 
557
            self._parents_map = {}
 
558
            if 'hpss' in debug.debug_flags:
 
559
                self._requested_parents = set()
584
560
        elif self._lock_mode == 'r':
585
561
            raise errors.ReadOnlyError(self)
586
562
        else:
603
579
        :param repository: The repository to fallback to for non-hpss
604
580
            implemented operations.
605
581
        """
606
 
        if self._real_repository is not None:
607
 
            raise AssertionError('_real_repository is already set')
608
582
        if isinstance(repository, RemoteRepository):
609
583
            raise AssertionError()
610
584
        self._real_repository = repository
611
 
        for fb in self._fallback_repositories:
612
 
            self._real_repository.add_fallback_repository(fb)
613
585
        if self._lock_mode == 'w':
614
586
            # if we are already locked, the real repository must be able to
615
587
            # acquire the lock with our token.
633
605
        if not token:
634
606
            # with no token the remote repository is not persistently locked.
635
607
            return
636
 
        err_context = {'token': token}
637
 
        response = self._call('Repository.unlock', path, token,
638
 
                              **err_context)
 
608
        try:
 
609
            response = self._client.call('Repository.unlock', path, token)
 
610
        except errors.ErrorFromSmartServer, err:
 
611
            if err.error_verb == 'TokenMismatch':
 
612
                raise errors.TokenMismatch(token, '(remote token)')
 
613
            raise
639
614
        if response == ('ok',):
640
615
            return
641
616
        else:
645
620
        self._lock_count -= 1
646
621
        if self._lock_count > 0:
647
622
            return
648
 
        self._unstacked_provider.disable_cache()
 
623
        self._parents_map = None
 
624
        if 'hpss' in debug.debug_flags:
 
625
            self._requested_parents = None
649
626
        old_mode = self._lock_mode
650
627
        self._lock_mode = None
651
628
        try:
680
657
        import tempfile
681
658
        path = self.bzrdir._path_for_remote_call(self._client)
682
659
        try:
683
 
            response, protocol = self._call_expecting_body(
 
660
            response, protocol = self._client.call_expecting_body(
684
661
                'Repository.tarball', path, compression)
685
662
        except errors.UnknownSmartMethod:
686
663
            protocol.cancel_read_body()
718
695
        # FIXME: It ought to be possible to call this without immediately
719
696
        # triggering _ensure_real.  For now it's the easiest thing to do.
720
697
        self._ensure_real()
721
 
        real_repo = self._real_repository
722
 
        builder = real_repo.get_commit_builder(branch, parents,
 
698
        builder = self._real_repository.get_commit_builder(branch, parents,
723
699
                config, timestamp=timestamp, timezone=timezone,
724
700
                committer=committer, revprops=revprops, revision_id=revision_id)
725
701
        return builder
726
702
 
727
 
    def add_fallback_repository(self, repository):
728
 
        """Add a repository to use for looking up data not held locally.
729
 
        
730
 
        :param repository: A repository.
731
 
        """
732
 
        # XXX: At the moment the RemoteRepository will allow fallbacks
733
 
        # unconditionally - however, a _real_repository will usually exist,
734
 
        # and may raise an error if it's not accommodated by the underlying
735
 
        # format.  Eventually we should check when opening the repository
736
 
        # whether it's willing to allow them or not.
737
 
        #
738
 
        # We need to accumulate additional repositories here, to pass them in
739
 
        # on various RPC's.
740
 
        self._fallback_repositories.append(repository)
741
 
        # They are also seen by the fallback repository.  If it doesn't exist
742
 
        # yet they'll be added then.  This implicitly copies them.
743
 
        self._ensure_real()
744
 
 
745
703
    def add_inventory(self, revid, inv, parents):
746
704
        self._ensure_real()
747
705
        return self._real_repository.add_inventory(revid, inv, parents)
748
706
 
749
 
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
750
 
                               parents):
751
 
        self._ensure_real()
752
 
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
753
 
            delta, new_revision_id, parents)
754
 
 
755
707
    def add_revision(self, rev_id, rev, inv=None, config=None):
756
708
        self._ensure_real()
757
709
        return self._real_repository.add_revision(
809
761
        return repository.InterRepository.get(
810
762
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
811
763
 
812
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
813
 
        # Not delegated to _real_repository so that InterRepository.get has a
814
 
        # chance to find an InterRepository specialised for RemoteRepository.
 
764
    def fetch(self, source, revision_id=None, pb=None):
815
765
        if self.has_same_location(source):
816
766
            # check that last_revision is in 'from' and then return a
817
767
            # no-operation.
819
769
                not revision.is_null(revision_id)):
820
770
                self.get_revision(revision_id)
821
771
            return 0, []
822
 
        inter = repository.InterRepository.get(source, self)
823
 
        try:
824
 
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
825
 
        except NotImplementedError:
826
 
            raise errors.IncompatibleRepositories(source, self)
 
772
        self._ensure_real()
 
773
        return self._real_repository.fetch(
 
774
            source, revision_id=revision_id, pb=pb)
827
775
 
828
776
    def create_bundle(self, target, base, fileobj, format=None):
829
777
        self._ensure_real()
849
797
        self._ensure_real()
850
798
        return self._real_repository.iter_files_bytes(desired_files)
851
799
 
852
 
    @property
853
 
    def _fetch_order(self):
854
 
        """Decorate the real repository for now.
855
 
 
856
 
        In the long term getting this back from the remote repository as part
857
 
        of open would be more efficient.
858
 
        """
859
 
        self._ensure_real()
860
 
        return self._real_repository._fetch_order
861
 
 
862
 
    @property
863
 
    def _fetch_uses_deltas(self):
864
 
        """Decorate the real repository for now.
865
 
 
866
 
        In the long term getting this back from the remote repository as part
867
 
        of open would be more efficient.
868
 
        """
869
 
        self._ensure_real()
870
 
        return self._real_repository._fetch_uses_deltas
871
 
 
872
 
    @property
873
 
    def _fetch_reconcile(self):
874
 
        """Decorate the real repository for now.
875
 
 
876
 
        In the long term getting this back from the remote repository as part
877
 
        of open would be more efficient.
878
 
        """
879
 
        self._ensure_real()
880
 
        return self._real_repository._fetch_reconcile
881
 
 
882
 
    def get_parent_map(self, revision_ids):
 
800
    def get_parent_map(self, keys):
883
801
        """See bzrlib.Graph.get_parent_map()."""
884
 
        return self._make_parents_provider().get_parent_map(revision_ids)
 
802
        # Hack to build up the caching logic.
 
803
        ancestry = self._parents_map
 
804
        if ancestry is None:
 
805
            # Repository is not locked, so there's no cache.
 
806
            missing_revisions = set(keys)
 
807
            ancestry = {}
 
808
        else:
 
809
            missing_revisions = set(key for key in keys if key not in ancestry)
 
810
        if missing_revisions:
 
811
            parent_map = self._get_parent_map(missing_revisions)
 
812
            if 'hpss' in debug.debug_flags:
 
813
                mutter('retransmitted revisions: %d of %d',
 
814
                        len(set(ancestry).intersection(parent_map)),
 
815
                        len(parent_map))
 
816
            ancestry.update(parent_map)
 
817
        present_keys = [k for k in keys if k in ancestry]
 
818
        if 'hpss' in debug.debug_flags:
 
819
            if self._requested_parents is not None and len(ancestry) != 0:
 
820
                self._requested_parents.update(present_keys)
 
821
                mutter('Current RemoteRepository graph hit rate: %d%%',
 
822
                    100.0 * len(self._requested_parents) / len(ancestry))
 
823
        return dict((k, ancestry[k]) for k in present_keys)
885
824
 
886
 
    def _get_parent_map_rpc(self, keys):
 
825
    def _get_parent_map(self, keys):
887
826
        """Helper for get_parent_map that performs the RPC."""
888
827
        medium = self._client._medium
889
828
        if medium._is_remote_before((1, 2)):
931
870
        # TODO: Manage this incrementally to avoid covering the same path
932
871
        # repeatedly. (The server will have to on each request, but the less
933
872
        # work done the better).
934
 
        parents_map = self._unstacked_provider.get_cached_map()
 
873
        parents_map = self._parents_map
935
874
        if parents_map is None:
936
875
            # Repository is not locked, so there's no cache.
937
876
            parents_map = {}
952
891
        verb = 'Repository.get_parent_map'
953
892
        args = (path,) + tuple(keys)
954
893
        try:
955
 
            response = self._call_with_body_bytes_expecting_body(
956
 
                verb, args, body)
 
894
            response = self._client.call_with_body_bytes_expecting_body(
 
895
                verb, args, self._serialise_search_recipe(recipe))
957
896
        except errors.UnknownSmartMethod:
958
897
            # Server does not support this method, so get the whole graph.
959
898
            # Worse, we have to force a disconnection, because the server now
1051
990
        # destination
1052
991
        from bzrlib import osutils
1053
992
        import tarfile
 
993
        import tempfile
1054
994
        # TODO: Maybe a progress bar while streaming the tarball?
1055
995
        note("Copying repository content as tarball...")
1056
996
        tar_file = self._get_tarball('bz2')
1060
1000
        try:
1061
1001
            tar = tarfile.open('repository', fileobj=tar_file,
1062
1002
                mode='r|bz2')
1063
 
            tmpdir = osutils.mkdtemp()
 
1003
            tmpdir = tempfile.mkdtemp()
1064
1004
            try:
1065
1005
                _extract_tar(tar, tmpdir)
1066
1006
                tmp_bzrdir = BzrDir.open(tmpdir)
1183
1123
        self._ensure_real()
1184
1124
        return self._real_repository._check_for_inconsistent_revision_parents()
1185
1125
 
1186
 
    def _make_parents_provider(self, other=None):
1187
 
        providers = [self._unstacked_provider]
1188
 
        if other is not None:
1189
 
            providers.insert(0, other)
1190
 
        providers.extend(r._make_parents_provider() for r in
1191
 
                         self._fallback_repositories)
1192
 
        return graph._StackedParentsProvider(providers)
 
1126
    def _make_parents_provider(self):
 
1127
        return self
1193
1128
 
1194
1129
    def _serialise_search_recipe(self, recipe):
1195
1130
        """Serialise a graph search recipe.
1202
1137
        count = str(recipe[2])
1203
1138
        return '\n'.join((start_keys, stop_keys, count))
1204
1139
 
1205
 
    def autopack(self):
1206
 
        path = self.bzrdir._path_for_remote_call(self._client)
1207
 
        try:
1208
 
            response = self._call('PackRepository.autopack', path)
1209
 
        except errors.UnknownSmartMethod:
1210
 
            self._ensure_real()
1211
 
            self._real_repository._pack_collection.autopack()
1212
 
            return
1213
 
        if self._real_repository is not None:
1214
 
            # Reset the real repository's cache of pack names.
1215
 
            # XXX: At some point we may be able to skip this and just rely on
1216
 
            # the automatic retry logic to do the right thing, but for now we
1217
 
            # err on the side of being correct rather than being optimal.
1218
 
            self._real_repository._pack_collection.reload_pack_names()
1219
 
        if response[0] != 'ok':
1220
 
            raise errors.UnexpectedSmartServerResponse(response)
1221
 
 
1222
1140
 
1223
1141
class RemoteBranchLockableFiles(LockableFiles):
1224
1142
    """A 'LockableFiles' implementation that talks to a smart server.
1242
1160
 
1243
1161
class RemoteBranchFormat(branch.BranchFormat):
1244
1162
 
1245
 
    def __init__(self):
1246
 
        super(RemoteBranchFormat, self).__init__()
1247
 
        self._matchingbzrdir = RemoteBzrDirFormat()
1248
 
        self._matchingbzrdir.set_branch_format(self)
1249
 
 
1250
1163
    def __eq__(self, other):
1251
1164
        return (isinstance(other, RemoteBranchFormat) and 
1252
1165
            self.__dict__ == other.__dict__)
1269
1182
        return True
1270
1183
 
1271
1184
 
1272
 
class RemoteBranch(branch.Branch, _RpcHelper):
 
1185
class RemoteBranch(branch.Branch):
1273
1186
    """Branch stored on a server accessed by HPSS RPC.
1274
1187
 
1275
1188
    At the moment most operations are mapped down to simple file operations.
1287
1200
        # will try to assign to self.tags, which is a property in this subclass.
1288
1201
        # And the parent's __init__ doesn't do much anyway.
1289
1202
        self._revision_id_to_revno_cache = None
1290
 
        self._partial_revision_id_to_revno_cache = {}
1291
1203
        self._revision_history_cache = None
1292
1204
        self._last_revision_info_cache = None
1293
 
        self._merge_sorted_revisions_cache = None
1294
1205
        self.bzrdir = remote_bzrdir
1295
1206
        if _client is not None:
1296
1207
            self._client = _client
1318
1229
        self._repo_lock_token = None
1319
1230
        self._lock_count = 0
1320
1231
        self._leave_lock = False
1321
 
        # The base class init is not called, so we duplicate this:
1322
 
        hooks = branch.Branch.hooks['open']
1323
 
        for hook in hooks:
1324
 
            hook(self)
1325
 
        self._setup_stacking()
1326
 
 
1327
 
    def _setup_stacking(self):
1328
 
        # configure stacking into the remote repository, by reading it from
1329
 
        # the vfs branch.
1330
 
        try:
1331
 
            fallback_url = self.get_stacked_on_url()
1332
 
        except (errors.NotStacked, errors.UnstackableBranchFormat,
1333
 
            errors.UnstackableRepositoryFormat), e:
1334
 
            return
1335
 
        # it's relative to this branch...
1336
 
        fallback_url = urlutils.join(self.base, fallback_url)
1337
 
        transports = [self.bzrdir.root_transport]
1338
 
        if self._real_branch is not None:
1339
 
            transports.append(self._real_branch._transport)
1340
 
        stacked_on = branch.Branch.open(fallback_url,
1341
 
                                        possible_transports=transports)
1342
 
        self.repository.add_fallback_repository(stacked_on.repository)
1343
1232
 
1344
1233
    def _get_real_transport(self):
1345
1234
        # if we try vfs access, return the real branch's vfs transport
1364
1253
                    'to use vfs implementation')
1365
1254
            self.bzrdir._ensure_real()
1366
1255
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1367
 
            if self.repository._real_repository is None:
1368
 
                # Give the remote repository the matching real repo.
1369
 
                real_repo = self._real_branch.repository
1370
 
                if isinstance(real_repo, RemoteRepository):
1371
 
                    real_repo._ensure_real()
1372
 
                    real_repo = real_repo._real_repository
1373
 
                self.repository._set_real_repository(real_repo)
1374
 
            # Give the real branch the remote repository to let fast-pathing
1375
 
            # happen.
 
1256
            # Give the remote repository the matching real repo.
 
1257
            real_repo = self._real_branch.repository
 
1258
            if isinstance(real_repo, RemoteRepository):
 
1259
                real_repo._ensure_real()
 
1260
                real_repo = real_repo._real_repository
 
1261
            self.repository._set_real_repository(real_repo)
 
1262
            # Give the branch the remote repository to let fast-pathing happen.
1376
1263
            self._real_branch.repository = self.repository
 
1264
            # XXX: deal with _lock_mode == 'w'
1377
1265
            if self._lock_mode == 'r':
1378
1266
                self._real_branch.lock_read()
1379
 
            elif self._lock_mode == 'w':
1380
 
                self._real_branch.lock_write(token=self._lock_token)
1381
 
 
1382
 
    def _translate_error(self, err, **context):
1383
 
        self.repository._translate_error(err, branch=self, **context)
1384
1267
 
1385
1268
    def _clear_cached_state(self):
1386
1269
        super(RemoteBranch, self)._clear_cached_state()
1418
1301
        self._ensure_real()
1419
1302
        return self._real_branch.get_physical_lock_status()
1420
1303
 
1421
 
    def get_stacked_on_url(self):
1422
 
        """Get the URL this branch is stacked against.
1423
 
 
1424
 
        :raises NotStacked: If the branch is not stacked.
1425
 
        :raises UnstackableBranchFormat: If the branch does not support
1426
 
            stacking.
1427
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1428
 
            stacking.
1429
 
        """
1430
 
        try:
1431
 
            # there may not be a repository yet, so we can't use
1432
 
            # self._translate_error, so we can't use self._call either.
1433
 
            response = self._client.call('Branch.get_stacked_on_url',
1434
 
                self._remote_path())
1435
 
        except errors.ErrorFromSmartServer, err:
1436
 
            # there may not be a repository yet, so we can't call through
1437
 
            # its _translate_error
1438
 
            _translate_error(err, branch=self)
1439
 
        except errors.UnknownSmartMethod, err:
1440
 
            self._ensure_real()
1441
 
            return self._real_branch.get_stacked_on_url()
1442
 
        if response[0] != 'ok':
1443
 
            raise errors.UnexpectedSmartServerResponse(response)
1444
 
        return response[1]
1445
 
 
1446
1304
    def lock_read(self):
1447
 
        self.repository.lock_read()
1448
1305
        if not self._lock_mode:
1449
1306
            self._lock_mode = 'r'
1450
1307
            self._lock_count = 1
1460
1317
            branch_token = token
1461
1318
            repo_token = self.repository.lock_write()
1462
1319
            self.repository.unlock()
1463
 
        err_context = {'token': token}
1464
 
        response = self._call(
1465
 
            'Branch.lock_write', self._remote_path(), branch_token,
1466
 
            repo_token or '', **err_context)
 
1320
        path = self.bzrdir._path_for_remote_call(self._client)
 
1321
        try:
 
1322
            response = self._client.call(
 
1323
                'Branch.lock_write', path, branch_token, repo_token or '')
 
1324
        except errors.ErrorFromSmartServer, err:
 
1325
            if err.error_verb == 'LockContention':
 
1326
                raise errors.LockContention('(remote lock)')
 
1327
            elif err.error_verb == 'TokenMismatch':
 
1328
                raise errors.TokenMismatch(token, '(remote token)')
 
1329
            elif err.error_verb == 'UnlockableTransport':
 
1330
                raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
1331
            elif err.error_verb == 'ReadOnlyError':
 
1332
                raise errors.ReadOnlyError(self)
 
1333
            elif err.error_verb == 'LockFailed':
 
1334
                raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
1335
            raise
1467
1336
        if response[0] != 'ok':
1468
1337
            raise errors.UnexpectedSmartServerResponse(response)
1469
1338
        ok, branch_token, repo_token = response
1471
1340
            
1472
1341
    def lock_write(self, token=None):
1473
1342
        if not self._lock_mode:
1474
 
            # Lock the branch and repo in one remote call.
1475
1343
            remote_tokens = self._remote_lock_write(token)
1476
1344
            self._lock_token, self._repo_lock_token = remote_tokens
1477
1345
            if not self._lock_token:
1478
1346
                raise SmartProtocolError('Remote server did not return a token!')
1479
 
            # Tell the self.repository object that it is locked.
1480
 
            self.repository.lock_write(
1481
 
                self._repo_lock_token, _skip_rpc=True)
1482
 
 
 
1347
            # TODO: We really, really, really don't want to call _ensure_real
 
1348
            # here, but it's the easiest way to ensure coherency between the
 
1349
            # state of the RemoteBranch and RemoteRepository objects and the
 
1350
            # physical locks.  If we don't materialise the real objects here,
 
1351
            # then getting everything in the right state later is complex, so
 
1352
            # for now we just do it the lazy way.
 
1353
            #   -- Andrew Bennetts, 2007-02-22.
 
1354
            self._ensure_real()
1483
1355
            if self._real_branch is not None:
1484
 
                self._real_branch.lock_write(token=self._lock_token)
 
1356
                self._real_branch.repository.lock_write(
 
1357
                    token=self._repo_lock_token)
 
1358
                try:
 
1359
                    self._real_branch.lock_write(token=self._lock_token)
 
1360
                finally:
 
1361
                    self._real_branch.repository.unlock()
1485
1362
            if token is not None:
1486
1363
                self._leave_lock = True
1487
1364
            else:
 
1365
                # XXX: this case seems to be unreachable; token cannot be None.
1488
1366
                self._leave_lock = False
1489
1367
            self._lock_mode = 'w'
1490
1368
            self._lock_count = 1
1492
1370
            raise errors.ReadOnlyTransaction
1493
1371
        else:
1494
1372
            if token is not None:
1495
 
                # A token was given to lock_write, and we're relocking, so
1496
 
                # check that the given token actually matches the one we
1497
 
                # already have.
 
1373
                # A token was given to lock_write, and we're relocking, so check
 
1374
                # that the given token actually matches the one we already have.
1498
1375
                if token != self._lock_token:
1499
1376
                    raise errors.TokenMismatch(token, self._lock_token)
1500
1377
            self._lock_count += 1
1501
 
            # Re-lock the repository too.
1502
 
            self.repository.lock_write(self._repo_lock_token)
1503
1378
        return self._lock_token or None
1504
1379
 
1505
1380
    def _unlock(self, branch_token, repo_token):
1506
 
        err_context = {'token': str((branch_token, repo_token))}
1507
 
        response = self._call(
1508
 
            'Branch.unlock', self._remote_path(), branch_token,
1509
 
            repo_token or '', **err_context)
 
1381
        path = self.bzrdir._path_for_remote_call(self._client)
 
1382
        try:
 
1383
            response = self._client.call('Branch.unlock', path, branch_token,
 
1384
                                         repo_token or '')
 
1385
        except errors.ErrorFromSmartServer, err:
 
1386
            if err.error_verb == 'TokenMismatch':
 
1387
                raise errors.TokenMismatch(
 
1388
                    str((branch_token, repo_token)), '(remote tokens)')
 
1389
            raise
1510
1390
        if response == ('ok',):
1511
1391
            return
1512
1392
        raise errors.UnexpectedSmartServerResponse(response)
1513
1393
 
1514
1394
    def unlock(self):
1515
 
        try:
1516
 
            self._lock_count -= 1
1517
 
            if not self._lock_count:
1518
 
                self._clear_cached_state()
1519
 
                mode = self._lock_mode
1520
 
                self._lock_mode = None
1521
 
                if self._real_branch is not None:
1522
 
                    if (not self._leave_lock and mode == 'w' and
1523
 
                        self._repo_lock_token):
1524
 
                        # If this RemoteBranch will remove the physical lock
1525
 
                        # for the repository, make sure the _real_branch
1526
 
                        # doesn't do it first.  (Because the _real_branch's
1527
 
                        # repository is set to be the RemoteRepository.)
1528
 
                        self._real_branch.repository.leave_lock_in_place()
1529
 
                    self._real_branch.unlock()
1530
 
                if mode != 'w':
1531
 
                    # Only write-locked branched need to make a remote method
1532
 
                    # call to perfom the unlock.
1533
 
                    return
1534
 
                if not self._lock_token:
1535
 
                    raise AssertionError('Locked, but no token!')
1536
 
                branch_token = self._lock_token
1537
 
                repo_token = self._repo_lock_token
1538
 
                self._lock_token = None
1539
 
                self._repo_lock_token = None
1540
 
                if not self._leave_lock:
1541
 
                    self._unlock(branch_token, repo_token)
1542
 
        finally:
1543
 
            self.repository.unlock()
 
1395
        self._lock_count -= 1
 
1396
        if not self._lock_count:
 
1397
            self._clear_cached_state()
 
1398
            mode = self._lock_mode
 
1399
            self._lock_mode = None
 
1400
            if self._real_branch is not None:
 
1401
                if (not self._leave_lock and mode == 'w' and
 
1402
                    self._repo_lock_token):
 
1403
                    # If this RemoteBranch will remove the physical lock for the
 
1404
                    # repository, make sure the _real_branch doesn't do it
 
1405
                    # first.  (Because the _real_branch's repository is set to
 
1406
                    # be the RemoteRepository.)
 
1407
                    self._real_branch.repository.leave_lock_in_place()
 
1408
                self._real_branch.unlock()
 
1409
            if mode != 'w':
 
1410
                # Only write-locked branched need to make a remote method call
 
1411
                # to perfom the unlock.
 
1412
                return
 
1413
            if not self._lock_token:
 
1414
                raise AssertionError('Locked, but no token!')
 
1415
            branch_token = self._lock_token
 
1416
            repo_token = self._repo_lock_token
 
1417
            self._lock_token = None
 
1418
            self._repo_lock_token = None
 
1419
            if not self._leave_lock:
 
1420
                self._unlock(branch_token, repo_token)
1544
1421
 
1545
1422
    def break_lock(self):
1546
1423
        self._ensure_real()
1557
1434
        self._leave_lock = False
1558
1435
 
1559
1436
    def _last_revision_info(self):
1560
 
        response = self._call('Branch.last_revision_info', self._remote_path())
 
1437
        path = self.bzrdir._path_for_remote_call(self._client)
 
1438
        response = self._client.call('Branch.last_revision_info', path)
1561
1439
        if response[0] != 'ok':
1562
1440
            raise SmartProtocolError('unexpected response code %s' % (response,))
1563
1441
        revno = int(response[1])
1566
1444
 
1567
1445
    def _gen_revision_history(self):
1568
1446
        """See Branch._gen_revision_history()."""
1569
 
        response_tuple, response_handler = self._call_expecting_body(
1570
 
            'Branch.revision_history', self._remote_path())
 
1447
        path = self.bzrdir._path_for_remote_call(self._client)
 
1448
        response_tuple, response_handler = self._client.call_expecting_body(
 
1449
            'Branch.revision_history', path)
1571
1450
        if response_tuple[0] != 'ok':
1572
1451
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1573
1452
        result = response_handler.read_body_bytes().split('\x00')
1575
1454
            return []
1576
1455
        return result
1577
1456
 
1578
 
    def _remote_path(self):
1579
 
        return self.bzrdir._path_for_remote_call(self._client)
1580
 
 
1581
1457
    def _set_last_revision_descendant(self, revision_id, other_branch,
1582
1458
            allow_diverged=False, allow_overwrite_descendant=False):
1583
 
        err_context = {'other_branch': other_branch}
1584
 
        response = self._call('Branch.set_last_revision_ex',
1585
 
            self._remote_path(), self._lock_token, self._repo_lock_token,
1586
 
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1587
 
            **err_context)
 
1459
        path = self.bzrdir._path_for_remote_call(self._client)
 
1460
        try:
 
1461
            response = self._client.call('Branch.set_last_revision_ex',
 
1462
                path, self._lock_token, self._repo_lock_token, revision_id,
 
1463
                int(allow_diverged), int(allow_overwrite_descendant))
 
1464
        except errors.ErrorFromSmartServer, err:
 
1465
            if err.error_verb == 'NoSuchRevision':
 
1466
                raise NoSuchRevision(self, revision_id)
 
1467
            elif err.error_verb == 'Diverged':
 
1468
                raise errors.DivergedBranches(self, other_branch)
 
1469
            raise
1588
1470
        self._clear_cached_state()
1589
1471
        if len(response) != 3 and response[0] != 'ok':
1590
1472
            raise errors.UnexpectedSmartServerResponse(response)
1591
1473
        new_revno, new_revision_id = response[1:]
1592
1474
        self._last_revision_info_cache = new_revno, new_revision_id
1593
 
        if self._real_branch is not None:
1594
 
            cache = new_revno, new_revision_id
1595
 
            self._real_branch._last_revision_info_cache = cache
 
1475
        self._real_branch._last_revision_info_cache = new_revno, new_revision_id
1596
1476
 
1597
1477
    def _set_last_revision(self, revision_id):
 
1478
        path = self.bzrdir._path_for_remote_call(self._client)
1598
1479
        self._clear_cached_state()
1599
 
        response = self._call('Branch.set_last_revision',
1600
 
            self._remote_path(), self._lock_token, self._repo_lock_token,
1601
 
            revision_id)
 
1480
        try:
 
1481
            response = self._client.call('Branch.set_last_revision',
 
1482
                path, self._lock_token, self._repo_lock_token, revision_id)
 
1483
        except errors.ErrorFromSmartServer, err:
 
1484
            if err.error_verb == 'NoSuchRevision':
 
1485
                raise NoSuchRevision(self, revision_id)
 
1486
            raise
1602
1487
        if response != ('ok',):
1603
1488
            raise errors.UnexpectedSmartServerResponse(response)
1604
1489
 
1622
1507
        self._ensure_real()
1623
1508
        return self._real_branch.set_parent(url)
1624
1509
        
1625
 
    def set_stacked_on_url(self, stacked_location):
1626
 
        """Set the URL this branch is stacked against.
1627
 
 
1628
 
        :raises UnstackableBranchFormat: If the branch does not support
1629
 
            stacking.
1630
 
        :raises UnstackableRepositoryFormat: If the repository does not support
1631
 
            stacking.
1632
 
        """
 
1510
    def sprout(self, to_bzrdir, revision_id=None):
 
1511
        # Like Branch.sprout, except that it sprouts a branch in the default
 
1512
        # format, because RemoteBranches can't be created at arbitrary URLs.
 
1513
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
 
1514
        # to_bzrdir.create_branch...
1633
1515
        self._ensure_real()
1634
 
        return self._real_branch.set_stacked_on_url(stacked_location)
1635
 
 
1636
 
    def sprout(self, to_bzrdir, revision_id=None):
1637
 
        branch_format = to_bzrdir._format._branch_format
1638
 
        if (branch_format is None or
1639
 
            isinstance(branch_format, RemoteBranchFormat)):
1640
 
            # The to_bzrdir specifies RemoteBranchFormat (or no format, which
1641
 
            # implies the same thing), but RemoteBranches can't be created at
1642
 
            # arbitrary URLs.  So create a branch in the same format as
1643
 
            # _real_branch instead.
1644
 
            # XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1645
 
            # to_bzrdir.create_branch to create a RemoteBranch after all...
1646
 
            self._ensure_real()
1647
 
            result = self._real_branch._format.initialize(to_bzrdir)
1648
 
            self.copy_content_into(result, revision_id=revision_id)
1649
 
            result.set_parent(self.bzrdir.root_transport.base)
1650
 
        else:
1651
 
            result = branch.Branch.sprout(
1652
 
                self, to_bzrdir, revision_id=revision_id)
 
1516
        result = self._real_branch._format.initialize(to_bzrdir)
 
1517
        self.copy_content_into(result, revision_id=revision_id)
 
1518
        result.set_parent(self.bzrdir.root_transport.base)
1653
1519
        return result
1654
1520
 
1655
1521
    @needs_write_lock
1671
1537
    def is_locked(self):
1672
1538
        return self._lock_count >= 1
1673
1539
 
1674
 
    @needs_read_lock
1675
 
    def revision_id_to_revno(self, revision_id):
1676
 
        self._ensure_real()
1677
 
        return self._real_branch.revision_id_to_revno(revision_id)
1678
 
 
1679
1540
    @needs_write_lock
1680
1541
    def set_last_revision_info(self, revno, revision_id):
1681
1542
        revision_id = ensure_null(revision_id)
 
1543
        path = self.bzrdir._path_for_remote_call(self._client)
1682
1544
        try:
1683
 
            response = self._call('Branch.set_last_revision_info',
1684
 
                self._remote_path(), self._lock_token, self._repo_lock_token,
1685
 
                str(revno), revision_id)
 
1545
            response = self._client.call('Branch.set_last_revision_info',
 
1546
                path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1686
1547
        except errors.UnknownSmartMethod:
1687
1548
            self._ensure_real()
1688
1549
            self._clear_cached_state_of_remote_branch_only()
1689
1550
            self._real_branch.set_last_revision_info(revno, revision_id)
1690
1551
            self._last_revision_info_cache = revno, revision_id
1691
1552
            return
 
1553
        except errors.ErrorFromSmartServer, err:
 
1554
            if err.error_verb == 'NoSuchRevision':
 
1555
                raise NoSuchRevision(self, err.error_args[0])
 
1556
            raise
1692
1557
        if response == ('ok',):
1693
1558
            self._clear_cached_state()
1694
1559
            self._last_revision_info_cache = revno, revision_id
1771
1636
    """
1772
1637
    for tarinfo in tar:
1773
1638
        tar.extract(tarinfo, to_dir)
1774
 
 
1775
 
 
1776
 
def _translate_error(err, **context):
1777
 
    """Translate an ErrorFromSmartServer into a more useful error.
1778
 
 
1779
 
    Possible context keys:
1780
 
      - branch
1781
 
      - repository
1782
 
      - bzrdir
1783
 
      - token
1784
 
      - other_branch
1785
 
      - path
1786
 
 
1787
 
    If the error from the server doesn't match a known pattern, then
1788
 
    UnknownErrorFromSmartServer is raised.
1789
 
    """
1790
 
    def find(name):
1791
 
        try:
1792
 
            return context[name]
1793
 
        except KeyError, key_err:
1794
 
            mutter('Missing key %r in context %r', key_err.args[0], context)
1795
 
            raise err
1796
 
    def get_path():
1797
 
        """Get the path from the context if present, otherwise use first error
1798
 
        arg.
1799
 
        """
1800
 
        try:
1801
 
            return context['path']
1802
 
        except KeyError, key_err:
1803
 
            try:
1804
 
                return err.error_args[0]
1805
 
            except IndexError, idx_err:
1806
 
                mutter(
1807
 
                    'Missing key %r in context %r', key_err.args[0], context)
1808
 
                raise err
1809
 
 
1810
 
    if err.error_verb == 'NoSuchRevision':
1811
 
        raise NoSuchRevision(find('branch'), err.error_args[0])
1812
 
    elif err.error_verb == 'nosuchrevision':
1813
 
        raise NoSuchRevision(find('repository'), err.error_args[0])
1814
 
    elif err.error_tuple == ('nobranch',):
1815
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
1816
 
    elif err.error_verb == 'norepository':
1817
 
        raise errors.NoRepositoryPresent(find('bzrdir'))
1818
 
    elif err.error_verb == 'LockContention':
1819
 
        raise errors.LockContention('(remote lock)')
1820
 
    elif err.error_verb == 'UnlockableTransport':
1821
 
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
1822
 
    elif err.error_verb == 'LockFailed':
1823
 
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
1824
 
    elif err.error_verb == 'TokenMismatch':
1825
 
        raise errors.TokenMismatch(find('token'), '(remote token)')
1826
 
    elif err.error_verb == 'Diverged':
1827
 
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
1828
 
    elif err.error_verb == 'TipChangeRejected':
1829
 
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
1830
 
    elif err.error_verb == 'UnstackableBranchFormat':
1831
 
        raise errors.UnstackableBranchFormat(*err.error_args)
1832
 
    elif err.error_verb == 'UnstackableRepositoryFormat':
1833
 
        raise errors.UnstackableRepositoryFormat(*err.error_args)
1834
 
    elif err.error_verb == 'NotStacked':
1835
 
        raise errors.NotStacked(branch=find('branch'))
1836
 
    elif err.error_verb == 'PermissionDenied':
1837
 
        path = get_path()
1838
 
        if len(err.error_args) >= 2:
1839
 
            extra = err.error_args[1]
1840
 
        else:
1841
 
            extra = None
1842
 
        raise errors.PermissionDenied(path, extra=extra)
1843
 
    elif err.error_verb == 'ReadError':
1844
 
        path = get_path()
1845
 
        raise errors.ReadError(path)
1846
 
    elif err.error_verb == 'NoSuchFile':
1847
 
        path = get_path()
1848
 
        raise errors.NoSuchFile(path)
1849
 
    elif err.error_verb == 'FileExists':
1850
 
        raise errors.FileExists(err.error_args[0])
1851
 
    elif err.error_verb == 'DirectoryNotEmpty':
1852
 
        raise errors.DirectoryNotEmpty(err.error_args[0])
1853
 
    elif err.error_verb == 'ShortReadvError':
1854
 
        args = err.error_args
1855
 
        raise errors.ShortReadvError(
1856
 
            args[0], int(args[1]), int(args[2]), int(args[3]))
1857
 
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
1858
 
        encoding = str(err.error_args[0]) # encoding must always be a string
1859
 
        val = err.error_args[1]
1860
 
        start = int(err.error_args[2])
1861
 
        end = int(err.error_args[3])
1862
 
        reason = str(err.error_args[4]) # reason must always be a string
1863
 
        if val.startswith('u:'):
1864
 
            val = val[2:].decode('utf-8')
1865
 
        elif val.startswith('s:'):
1866
 
            val = val[2:].decode('base64')
1867
 
        if err.error_verb == 'UnicodeDecodeError':
1868
 
            raise UnicodeDecodeError(encoding, val, start, end, reason)
1869
 
        elif err.error_verb == 'UnicodeEncodeError':
1870
 
            raise UnicodeEncodeError(encoding, val, start, end, reason)
1871
 
    elif err.error_verb == 'ReadOnlyError':
1872
 
        raise errors.TransportNotPossible('readonly transport')
1873
 
    raise errors.UnknownErrorFromSmartServer(err)