~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Martin Packman
  • Date: 2012-01-05 10:37:58 UTC
  • mto: This revision was merged to the branch mainline in revision 6427.
  • Revision ID: martin.packman@canonical.com-20120105103758-wzftnmsip5iv9n2g
Revert addition of get_message_encoding function

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import bz2
 
18
import zlib
18
19
 
19
20
from bzrlib import (
20
21
    bencode,
24
25
    controldir,
25
26
    debug,
26
27
    errors,
 
28
    gpg,
27
29
    graph,
 
30
    inventory_delta,
28
31
    lock,
29
32
    lockdir,
 
33
    osutils,
 
34
    registry,
30
35
    repository as _mod_repository,
31
36
    revision as _mod_revision,
32
37
    static_tuple,
33
38
    symbol_versioning,
 
39
    testament as _mod_testament,
34
40
    urlutils,
35
41
    vf_repository,
 
42
    vf_search,
36
43
    )
37
44
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
38
45
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
40
47
    NoSuchRevision,
41
48
    SmartProtocolError,
42
49
    )
 
50
from bzrlib.i18n import gettext
 
51
from bzrlib.inventory import Inventory
43
52
from bzrlib.lockable_files import LockableFiles
44
53
from bzrlib.smart import client, vfs, repository as smart_repo
45
54
from bzrlib.smart.client import _SmartClient
46
55
from bzrlib.revision import NULL_REVISION
 
56
from bzrlib.revisiontree import InventoryRevisionTree
47
57
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
48
 
from bzrlib.trace import mutter, note, warning
 
58
from bzrlib.serializer import format_registry as serializer_format_registry
 
59
from bzrlib.trace import mutter, note, warning, log_exception_quietly
 
60
 
 
61
 
 
62
_DEFAULT_SEARCH_DEPTH = 100
49
63
 
50
64
 
51
65
class _RpcHelper(object):
110
124
 
111
125
    def get_format_description(self):
112
126
        if self._network_name:
113
 
            real_format = controldir.network_format_registry.get(self._network_name)
114
 
            return 'Remote: ' + real_format.get_format_description()
 
127
            try:
 
128
                real_format = controldir.network_format_registry.get(
 
129
                        self._network_name)
 
130
            except KeyError:
 
131
                pass
 
132
            else:
 
133
                return 'Remote: ' + real_format.get_format_description()
115
134
        return 'bzr remote bzrdir'
116
135
 
117
136
    def get_format_string(self):
327
346
        _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
328
347
 
329
348
 
 
349
class RemoteControlStore(config.IniFileStore):
 
350
    """Control store which attempts to use HPSS calls to retrieve control store.
 
351
 
 
352
    Note that this is specific to bzr-based formats.
 
353
    """
 
354
 
 
355
    def __init__(self, bzrdir):
 
356
        super(RemoteControlStore, self).__init__()
 
357
        self.bzrdir = bzrdir
 
358
        self._real_store = None
 
359
 
 
360
    def lock_write(self, token=None):
 
361
        self._ensure_real()
 
362
        return self._real_store.lock_write(token)
 
363
 
 
364
    def unlock(self):
 
365
        self._ensure_real()
 
366
        return self._real_store.unlock()
 
367
 
 
368
    @needs_write_lock
 
369
    def save(self):
 
370
        # We need to be able to override the undecorated implementation
 
371
        self.save_without_locking()
 
372
 
 
373
    def save_without_locking(self):
 
374
        super(RemoteControlStore, self).save()
 
375
 
 
376
    def _ensure_real(self):
 
377
        self.bzrdir._ensure_real()
 
378
        if self._real_store is None:
 
379
            self._real_store = config.ControlStore(self.bzrdir)
 
380
 
 
381
    def external_url(self):
 
382
        return self.bzrdir.user_url
 
383
 
 
384
    def _load_content(self):
 
385
        medium = self.bzrdir._client._medium
 
386
        path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
 
387
        try:
 
388
            response, handler = self.bzrdir._call_expecting_body(
 
389
                'BzrDir.get_config_file', path)
 
390
        except errors.UnknownSmartMethod:
 
391
            self._ensure_real()
 
392
            return self._real_store._load_content()
 
393
        if len(response) and response[0] != 'ok':
 
394
            raise errors.UnexpectedSmartServerResponse(response)
 
395
        return handler.read_body_bytes()
 
396
 
 
397
    def _save_content(self, content):
 
398
        # FIXME JRV 2011-11-22: Ideally this should use a
 
399
        # HPSS call too, but at the moment it is not possible
 
400
        # to write lock control directories.
 
401
        self._ensure_real()
 
402
        return self._real_store._save_content(content)
 
403
 
 
404
 
330
405
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
331
406
    """Control directory on a remote server, accessed via bzr:// or similar."""
332
407
 
416
491
        self._next_open_branch_result = None
417
492
        return _mod_bzrdir.BzrDir.break_lock(self)
418
493
 
 
494
    def _vfs_checkout_metadir(self):
 
495
        self._ensure_real()
 
496
        return self._real_bzrdir.checkout_metadir()
 
497
 
 
498
    def checkout_metadir(self):
 
499
        """Retrieve the controldir format to use for checkouts of this one.
 
500
        """
 
501
        medium = self._client._medium
 
502
        if medium._is_remote_before((2, 5)):
 
503
            return self._vfs_checkout_metadir()
 
504
        path = self._path_for_remote_call(self._client)
 
505
        try:
 
506
            response = self._client.call('BzrDir.checkout_metadir',
 
507
                path)
 
508
        except errors.UnknownSmartMethod:
 
509
            medium._remember_remote_is_before((2, 5))
 
510
            return self._vfs_checkout_metadir()
 
511
        if len(response) != 3:
 
512
            raise errors.UnexpectedSmartServerResponse(response)
 
513
        control_name, repo_name, branch_name = response
 
514
        try:
 
515
            format = controldir.network_format_registry.get(control_name)
 
516
        except KeyError:
 
517
            raise errors.UnknownFormatError(kind='control',
 
518
                format=control_name)
 
519
        if repo_name:
 
520
            try:
 
521
                repo_format = _mod_repository.network_format_registry.get(
 
522
                    repo_name)
 
523
            except KeyError:
 
524
                raise errors.UnknownFormatError(kind='repository',
 
525
                    format=repo_name)
 
526
            format.repository_format = repo_format
 
527
        if branch_name:
 
528
            try:
 
529
                format.set_branch_format(
 
530
                    branch.network_format_registry.get(branch_name))
 
531
            except KeyError:
 
532
                raise errors.UnknownFormatError(kind='branch',
 
533
                    format=branch_name)
 
534
        return format
 
535
 
419
536
    def _vfs_cloning_metadir(self, require_stacking=False):
420
537
        self._ensure_real()
421
538
        return self._real_bzrdir.cloning_metadir(
451
568
        if len(branch_info) != 2:
452
569
            raise errors.UnexpectedSmartServerResponse(response)
453
570
        branch_ref, branch_name = branch_info
454
 
        format = controldir.network_format_registry.get(control_name)
 
571
        try:
 
572
            format = controldir.network_format_registry.get(control_name)
 
573
        except KeyError:
 
574
            raise errors.UnknownFormatError(kind='control', format=control_name)
 
575
 
455
576
        if repo_name:
456
 
            format.repository_format = _mod_repository.network_format_registry.get(
457
 
                repo_name)
 
577
            try:
 
578
                format.repository_format = _mod_repository.network_format_registry.get(
 
579
                    repo_name)
 
580
            except KeyError:
 
581
                raise errors.UnknownFormatError(kind='repository',
 
582
                    format=repo_name)
458
583
        if branch_ref == 'ref':
459
584
            # XXX: we need possible_transports here to avoid reopening the
460
585
            # connection to the referenced location
463
588
            format.set_branch_format(branch_format)
464
589
        elif branch_ref == 'branch':
465
590
            if branch_name:
466
 
                format.set_branch_format(
467
 
                    branch.network_format_registry.get(branch_name))
 
591
                try:
 
592
                    branch_format = branch.network_format_registry.get(
 
593
                        branch_name)
 
594
                except KeyError:
 
595
                    raise errors.UnknownFormatError(kind='branch',
 
596
                        format=branch_name)
 
597
                format.set_branch_format(branch_format)
468
598
        else:
469
599
            raise errors.UnexpectedSmartServerResponse(response)
470
600
        return format
480
610
 
481
611
    def destroy_repository(self):
482
612
        """See BzrDir.destroy_repository"""
483
 
        self._ensure_real()
484
 
        self._real_bzrdir.destroy_repository()
 
613
        path = self._path_for_remote_call(self._client)
 
614
        try:
 
615
            response = self._call('BzrDir.destroy_repository', path)
 
616
        except errors.UnknownSmartMethod:
 
617
            self._ensure_real()
 
618
            self._real_bzrdir.destroy_repository()
 
619
            return
 
620
        if response[0] != 'ok':
 
621
            raise SmartProtocolError('unexpected response code %s' % (response,))
485
622
 
486
 
    def create_branch(self, name=None, repository=None):
 
623
    def create_branch(self, name=None, repository=None,
 
624
                      append_revisions_only=None):
487
625
        # as per meta1 formats - just delegate to the format object which may
488
626
        # be parameterised.
489
627
        real_branch = self._format.get_branch_format().initialize(self,
490
 
            name=name, repository=repository)
 
628
            name=name, repository=repository,
 
629
            append_revisions_only=append_revisions_only)
491
630
        if not isinstance(real_branch, RemoteBranch):
492
631
            if not isinstance(repository, RemoteRepository):
493
632
                raise AssertionError(
507
646
 
508
647
    def destroy_branch(self, name=None):
509
648
        """See BzrDir.destroy_branch"""
510
 
        self._ensure_real()
511
 
        self._real_bzrdir.destroy_branch(name=name)
 
649
        path = self._path_for_remote_call(self._client)
 
650
        try:
 
651
            if name is not None:
 
652
                args = (name, )
 
653
            else:
 
654
                args = ()
 
655
            response = self._call('BzrDir.destroy_branch', path, *args)
 
656
        except errors.UnknownSmartMethod:
 
657
            self._ensure_real()
 
658
            self._real_bzrdir.destroy_branch(name=name)
 
659
            self._next_open_branch_result = None
 
660
            return
512
661
        self._next_open_branch_result = None
 
662
        if response[0] != 'ok':
 
663
            raise SmartProtocolError('unexpected response code %s' % (response,))
513
664
 
514
665
    def create_workingtree(self, revision_id=None, from_branch=None,
515
666
        accelerator_tree=None, hardlink=False):
569
720
        return None, self.open_branch(name=name)
570
721
 
571
722
    def open_branch(self, name=None, unsupported=False,
572
 
                    ignore_fallbacks=False):
 
723
                    ignore_fallbacks=False, possible_transports=None):
573
724
        if unsupported:
574
725
            raise NotImplementedError('unsupported flag support not implemented yet.')
575
726
        if self._next_open_branch_result is not None:
582
733
            # a branch reference, use the existing BranchReference logic.
583
734
            format = BranchReferenceFormat()
584
735
            return format.open(self, name=name, _found=True,
585
 
                location=response[1], ignore_fallbacks=ignore_fallbacks)
 
736
                location=response[1], ignore_fallbacks=ignore_fallbacks,
 
737
                possible_transports=possible_transports)
586
738
        branch_format_name = response[1]
587
739
        if not branch_format_name:
588
740
            branch_format_name = None
589
741
        format = RemoteBranchFormat(network_name=branch_format_name)
590
742
        return RemoteBranch(self, self.find_repository(), format=format,
591
 
            setup_stacking=not ignore_fallbacks, name=name)
 
743
            setup_stacking=not ignore_fallbacks, name=name,
 
744
            possible_transports=possible_transports)
592
745
 
593
746
    def _open_repo_v1(self, path):
594
747
        verb = 'BzrDir.find_repository'
657
810
 
658
811
    def has_workingtree(self):
659
812
        if self._has_working_tree is None:
660
 
            self._ensure_real()
661
 
            self._has_working_tree = self._real_bzrdir.has_workingtree()
 
813
            path = self._path_for_remote_call(self._client)
 
814
            try:
 
815
                response = self._call('BzrDir.has_workingtree', path)
 
816
            except errors.UnknownSmartMethod:
 
817
                self._ensure_real()
 
818
                self._has_working_tree = self._real_bzrdir.has_workingtree()
 
819
            else:
 
820
                if response[0] not in ('yes', 'no'):
 
821
                    raise SmartProtocolError('unexpected response code %s' % (response,))
 
822
                self._has_working_tree = (response[0] == 'yes')
662
823
        return self._has_working_tree
663
824
 
664
825
    def open_workingtree(self, recommend_upgrade=True):
669
830
 
670
831
    def _path_for_remote_call(self, client):
671
832
        """Return the path to be used for this bzrdir in a remote call."""
672
 
        return client.remote_path_from_transport(self.root_transport)
 
833
        return urlutils.split_segment_parameters_raw(
 
834
            client.remote_path_from_transport(self.root_transport))[0]
673
835
 
674
836
    def get_branch_transport(self, branch_format, name=None):
675
837
        self._ensure_real()
691
853
        """Upgrading of remote bzrdirs is not supported yet."""
692
854
        return False
693
855
 
694
 
    def clone(self, url, revision_id=None, force_new_repo=False,
695
 
              preserve_stacking=False):
696
 
        self._ensure_real()
697
 
        return self._real_bzrdir.clone(url, revision_id=revision_id,
698
 
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
699
 
 
700
856
    def _get_config(self):
701
857
        return RemoteBzrDirConfig(self)
702
858
 
 
859
    def _get_config_store(self):
 
860
        return RemoteControlStore(self)
 
861
 
703
862
 
704
863
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
705
864
    """Format for repositories accessed over a _SmartClient.
735
894
        self._supports_external_lookups = None
736
895
        self._supports_tree_reference = None
737
896
        self._supports_funky_characters = None
 
897
        self._supports_nesting_repositories = None
738
898
        self._rich_root_data = None
739
899
 
740
900
    def __repr__(self):
777
937
        return self._supports_funky_characters
778
938
 
779
939
    @property
 
940
    def supports_nesting_repositories(self):
 
941
        if self._supports_nesting_repositories is None:
 
942
            self._ensure_real()
 
943
            self._supports_nesting_repositories = \
 
944
                self._custom_format.supports_nesting_repositories
 
945
        return self._supports_nesting_repositories
 
946
 
 
947
    @property
780
948
    def supports_tree_reference(self):
781
949
        if self._supports_tree_reference is None:
782
950
            self._ensure_real()
864
1032
 
865
1033
    def _ensure_real(self):
866
1034
        if self._custom_format is None:
867
 
            self._custom_format = _mod_repository.network_format_registry.get(
868
 
                self._network_name)
 
1035
            try:
 
1036
                self._custom_format = _mod_repository.network_format_registry.get(
 
1037
                    self._network_name)
 
1038
            except KeyError:
 
1039
                raise errors.UnknownFormatError(kind='repository',
 
1040
                    format=self._network_name)
869
1041
 
870
1042
    @property
871
1043
    def _fetch_order(self):
906
1078
        return self._custom_format._serializer
907
1079
 
908
1080
 
909
 
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
910
 
    controldir.ControlComponent):
 
1081
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
 
1082
        lock._RelockDebugMixin):
911
1083
    """Repository accessed over rpc.
912
1084
 
913
1085
    For the moment most operations are performed using local transport-backed
937
1109
        self._format = format
938
1110
        self._lock_mode = None
939
1111
        self._lock_token = None
 
1112
        self._write_group_tokens = None
940
1113
        self._lock_count = 0
941
1114
        self._leave_lock = False
942
1115
        # Cache of revision parents; misses are cached during read locks, and
982
1155
 
983
1156
        :param suppress_errors: see Repository.abort_write_group.
984
1157
        """
985
 
        self._ensure_real()
986
 
        return self._real_repository.abort_write_group(
987
 
            suppress_errors=suppress_errors)
 
1158
        if self._real_repository:
 
1159
            self._ensure_real()
 
1160
            return self._real_repository.abort_write_group(
 
1161
                suppress_errors=suppress_errors)
 
1162
        if not self.is_in_write_group():
 
1163
            if suppress_errors:
 
1164
                mutter('(suppressed) not in write group')
 
1165
                return
 
1166
            raise errors.BzrError("not in write group")
 
1167
        path = self.bzrdir._path_for_remote_call(self._client)
 
1168
        try:
 
1169
            response = self._call('Repository.abort_write_group', path,
 
1170
                self._lock_token, self._write_group_tokens)
 
1171
        except Exception, exc:
 
1172
            self._write_group = None
 
1173
            if not suppress_errors:
 
1174
                raise
 
1175
            mutter('abort_write_group failed')
 
1176
            log_exception_quietly()
 
1177
            note(gettext('bzr: ERROR (ignored): %s'), exc)
 
1178
        else:
 
1179
            if response != ('ok', ):
 
1180
                raise errors.UnexpectedSmartServerResponse(response)
 
1181
            self._write_group_tokens = None
988
1182
 
989
1183
    @property
990
1184
    def chk_bytes(self):
1004
1198
        for older plugins that don't use e.g. the CommitBuilder
1005
1199
        facility.
1006
1200
        """
1007
 
        self._ensure_real()
1008
 
        return self._real_repository.commit_write_group()
 
1201
        if self._real_repository:
 
1202
            self._ensure_real()
 
1203
            return self._real_repository.commit_write_group()
 
1204
        if not self.is_in_write_group():
 
1205
            raise errors.BzrError("not in write group")
 
1206
        path = self.bzrdir._path_for_remote_call(self._client)
 
1207
        response = self._call('Repository.commit_write_group', path,
 
1208
            self._lock_token, self._write_group_tokens)
 
1209
        if response != ('ok', ):
 
1210
            raise errors.UnexpectedSmartServerResponse(response)
 
1211
        self._write_group_tokens = None
1009
1212
 
1010
1213
    def resume_write_group(self, tokens):
1011
 
        self._ensure_real()
1012
 
        return self._real_repository.resume_write_group(tokens)
 
1214
        if self._real_repository:
 
1215
            return self._real_repository.resume_write_group(tokens)
 
1216
        path = self.bzrdir._path_for_remote_call(self._client)
 
1217
        try:
 
1218
            response = self._call('Repository.check_write_group', path,
 
1219
               self._lock_token, tokens)
 
1220
        except errors.UnknownSmartMethod:
 
1221
            self._ensure_real()
 
1222
            return self._real_repository.resume_write_group(tokens)
 
1223
        if response != ('ok', ):
 
1224
            raise errors.UnexpectedSmartServerResponse(response)
 
1225
        self._write_group_tokens = tokens
1013
1226
 
1014
1227
    def suspend_write_group(self):
1015
 
        self._ensure_real()
1016
 
        return self._real_repository.suspend_write_group()
 
1228
        if self._real_repository:
 
1229
            return self._real_repository.suspend_write_group()
 
1230
        ret = self._write_group_tokens or []
 
1231
        self._write_group_tokens = None
 
1232
        return ret
1017
1233
 
1018
1234
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1019
1235
        self._ensure_real()
1229
1445
 
1230
1446
    def get_physical_lock_status(self):
1231
1447
        """See Repository.get_physical_lock_status()."""
1232
 
        # should be an API call to the server.
1233
 
        self._ensure_real()
1234
 
        return self._real_repository.get_physical_lock_status()
 
1448
        path = self.bzrdir._path_for_remote_call(self._client)
 
1449
        try:
 
1450
            response = self._call('Repository.get_physical_lock_status', path)
 
1451
        except errors.UnknownSmartMethod:
 
1452
            self._ensure_real()
 
1453
            return self._real_repository.get_physical_lock_status()
 
1454
        if response[0] not in ('yes', 'no'):
 
1455
            raise errors.UnexpectedSmartServerResponse(response)
 
1456
        return (response[0] == 'yes')
1235
1457
 
1236
1458
    def is_in_write_group(self):
1237
1459
        """Return True if there is an open write group.
1238
1460
 
1239
1461
        write groups are only applicable locally for the smart server..
1240
1462
        """
 
1463
        if self._write_group_tokens is not None:
 
1464
            return True
1241
1465
        if self._real_repository:
1242
1466
            return self._real_repository.is_in_write_group()
1243
1467
 
1378
1602
            self._real_repository.lock_write(self._lock_token)
1379
1603
        elif self._lock_mode == 'r':
1380
1604
            self._real_repository.lock_read()
 
1605
        if self._write_group_tokens is not None:
 
1606
            # if we are already in a write group, resume it
 
1607
            self._real_repository.resume_write_group(self._write_group_tokens)
 
1608
            self._write_group_tokens = None
1381
1609
 
1382
1610
    def start_write_group(self):
1383
1611
        """Start a write group on the decorated repository.
1387
1615
        for older plugins that don't use e.g. the CommitBuilder
1388
1616
        facility.
1389
1617
        """
1390
 
        self._ensure_real()
1391
 
        return self._real_repository.start_write_group()
 
1618
        if self._real_repository:
 
1619
            self._ensure_real()
 
1620
            return self._real_repository.start_write_group()
 
1621
        if not self.is_write_locked():
 
1622
            raise errors.NotWriteLocked(self)
 
1623
        if self._write_group_tokens is not None:
 
1624
            raise errors.BzrError('already in a write group')
 
1625
        path = self.bzrdir._path_for_remote_call(self._client)
 
1626
        try:
 
1627
            response = self._call('Repository.start_write_group', path,
 
1628
                self._lock_token)
 
1629
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
 
1630
            self._ensure_real()
 
1631
            return self._real_repository.start_write_group()
 
1632
        if response[0] != 'ok':
 
1633
            raise errors.UnexpectedSmartServerResponse(response)
 
1634
        self._write_group_tokens = response[1]
1392
1635
 
1393
1636
    def _unlock(self, token):
1394
1637
        path = self.bzrdir._path_for_remote_call(self._client)
1421
1664
            # This is just to let the _real_repository stay up to date.
1422
1665
            if self._real_repository is not None:
1423
1666
                self._real_repository.unlock()
 
1667
            elif self._write_group_tokens is not None:
 
1668
                self.abort_write_group()
1424
1669
        finally:
1425
1670
            # The rpc-level lock should be released even if there was a
1426
1671
            # problem releasing the vfs-based lock.
1438
1683
 
1439
1684
    def break_lock(self):
1440
1685
        # should hand off to the network
1441
 
        self._ensure_real()
1442
 
        return self._real_repository.break_lock()
 
1686
        path = self.bzrdir._path_for_remote_call(self._client)
 
1687
        try:
 
1688
            response = self._call("Repository.break_lock", path)
 
1689
        except errors.UnknownSmartMethod:
 
1690
            self._ensure_real()
 
1691
            return self._real_repository.break_lock()
 
1692
        if response != ('ok',):
 
1693
            raise errors.UnexpectedSmartServerResponse(response)
1443
1694
 
1444
1695
    def _get_tarball(self, compression):
1445
1696
        """Return a TemporaryFile containing a repository tarball.
1463
1714
            return t
1464
1715
        raise errors.UnexpectedSmartServerResponse(response)
1465
1716
 
 
1717
    @needs_read_lock
1466
1718
    def sprout(self, to_bzrdir, revision_id=None):
1467
 
        # TODO: Option to control what format is created?
1468
 
        self._ensure_real()
1469
 
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
1470
 
                                                             shared=False)
 
1719
        """Create a descendent repository for new development.
 
1720
 
 
1721
        Unlike clone, this does not copy the settings of the repository.
 
1722
        """
 
1723
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1471
1724
        dest_repo.fetch(self, revision_id=revision_id)
1472
1725
        return dest_repo
1473
1726
 
 
1727
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
1728
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
1729
            # use target default format.
 
1730
            dest_repo = a_bzrdir.create_repository()
 
1731
        else:
 
1732
            # Most control formats need the repository to be specifically
 
1733
            # created, but on some old all-in-one formats it's not needed
 
1734
            try:
 
1735
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
 
1736
            except errors.UninitializableFormat:
 
1737
                dest_repo = a_bzrdir.open_repository()
 
1738
        return dest_repo
 
1739
 
1474
1740
    ### These methods are just thin shims to the VFS object for now.
1475
1741
 
 
1742
    @needs_read_lock
1476
1743
    def revision_tree(self, revision_id):
1477
 
        self._ensure_real()
1478
 
        return self._real_repository.revision_tree(revision_id)
 
1744
        revision_id = _mod_revision.ensure_null(revision_id)
 
1745
        if revision_id == _mod_revision.NULL_REVISION:
 
1746
            return InventoryRevisionTree(self,
 
1747
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
1748
        else:
 
1749
            return list(self.revision_trees([revision_id]))[0]
1479
1750
 
1480
1751
    def get_serializer_format(self):
1481
 
        self._ensure_real()
1482
 
        return self._real_repository.get_serializer_format()
 
1752
        path = self.bzrdir._path_for_remote_call(self._client)
 
1753
        try:
 
1754
            response = self._call('VersionedFileRepository.get_serializer_format',
 
1755
                path)
 
1756
        except errors.UnknownSmartMethod:
 
1757
            self._ensure_real()
 
1758
            return self._real_repository.get_serializer_format()
 
1759
        if response[0] != 'ok':
 
1760
            raise errors.UnexpectedSmartServerResponse(response)
 
1761
        return response[1]
1483
1762
 
1484
1763
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1485
1764
                           timezone=None, committer=None, revprops=None,
1505
1784
        # We need to accumulate additional repositories here, to pass them in
1506
1785
        # on various RPC's.
1507
1786
        #
 
1787
        # Make the check before we lock: this raises an exception.
 
1788
        self._check_fallback_repository(repository)
1508
1789
        if self.is_locked():
1509
1790
            # We will call fallback.unlock() when we transition to the unlocked
1510
1791
            # state, so always add a lock here. If a caller passes us a locked
1511
1792
            # repository, they are responsible for unlocking it later.
1512
1793
            repository.lock_read()
1513
 
        self._check_fallback_repository(repository)
1514
1794
        self._fallback_repositories.append(repository)
1515
1795
        # If self._real_repository was parameterised already (e.g. because a
1516
1796
        # _real_branch had its get_stacked_on_url method called), then the
1549
1829
 
1550
1830
    @needs_read_lock
1551
1831
    def get_inventory(self, revision_id):
 
1832
        return list(self.iter_inventories([revision_id]))[0]
 
1833
 
 
1834
    def _iter_inventories_rpc(self, revision_ids, ordering):
 
1835
        if ordering is None:
 
1836
            ordering = 'unordered'
 
1837
        path = self.bzrdir._path_for_remote_call(self._client)
 
1838
        body = "\n".join(revision_ids)
 
1839
        response_tuple, response_handler = (
 
1840
            self._call_with_body_bytes_expecting_body(
 
1841
                "VersionedFileRepository.get_inventories",
 
1842
                (path, ordering), body))
 
1843
        if response_tuple[0] != "ok":
 
1844
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
1845
        deserializer = inventory_delta.InventoryDeltaDeserializer()
 
1846
        byte_stream = response_handler.read_streamed_body()
 
1847
        decoded = smart_repo._byte_stream_to_stream(byte_stream)
 
1848
        if decoded is None:
 
1849
            # no results whatsoever
 
1850
            return
 
1851
        src_format, stream = decoded
 
1852
        if src_format.network_name() != self._format.network_name():
 
1853
            raise AssertionError(
 
1854
                "Mismatched RemoteRepository and stream src %r, %r" % (
 
1855
                src_format.network_name(), self._format.network_name()))
 
1856
        # ignore the src format, it's not really relevant
 
1857
        prev_inv = Inventory(root_id=None,
 
1858
            revision_id=_mod_revision.NULL_REVISION)
 
1859
        # there should be just one substream, with inventory deltas
 
1860
        substream_kind, substream = stream.next()
 
1861
        if substream_kind != "inventory-deltas":
 
1862
            raise AssertionError(
 
1863
                 "Unexpected stream %r received" % substream_kind)
 
1864
        for record in substream:
 
1865
            (parent_id, new_id, versioned_root, tree_references, invdelta) = (
 
1866
                deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
 
1867
            if parent_id != prev_inv.revision_id:
 
1868
                raise AssertionError("invalid base %r != %r" % (parent_id,
 
1869
                    prev_inv.revision_id))
 
1870
            inv = prev_inv.create_by_apply_delta(invdelta, new_id)
 
1871
            yield inv, inv.revision_id
 
1872
            prev_inv = inv
 
1873
 
 
1874
    def _iter_inventories_vfs(self, revision_ids, ordering=None):
1552
1875
        self._ensure_real()
1553
 
        return self._real_repository.get_inventory(revision_id)
 
1876
        return self._real_repository._iter_inventories(revision_ids, ordering)
1554
1877
 
1555
1878
    def iter_inventories(self, revision_ids, ordering=None):
1556
 
        self._ensure_real()
1557
 
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1879
        """Get many inventories by revision_ids.
 
1880
 
 
1881
        This will buffer some or all of the texts used in constructing the
 
1882
        inventories in memory, but will only parse a single inventory at a
 
1883
        time.
 
1884
 
 
1885
        :param revision_ids: The expected revision ids of the inventories.
 
1886
        :param ordering: optional ordering, e.g. 'topological'.  If not
 
1887
            specified, the order of revision_ids will be preserved (by
 
1888
            buffering if necessary).
 
1889
        :return: An iterator of inventories.
 
1890
        """
 
1891
        if ((None in revision_ids)
 
1892
            or (_mod_revision.NULL_REVISION in revision_ids)):
 
1893
            raise ValueError('cannot get null revision inventory')
 
1894
        for inv, revid in self._iter_inventories(revision_ids, ordering):
 
1895
            if inv is None:
 
1896
                raise errors.NoSuchRevision(self, revid)
 
1897
            yield inv
 
1898
 
 
1899
    def _iter_inventories(self, revision_ids, ordering=None):
 
1900
        if len(revision_ids) == 0:
 
1901
            return
 
1902
        missing = set(revision_ids)
 
1903
        if ordering is None:
 
1904
            order_as_requested = True
 
1905
            invs = {}
 
1906
            order = list(revision_ids)
 
1907
            order.reverse()
 
1908
            next_revid = order.pop()
 
1909
        else:
 
1910
            order_as_requested = False
 
1911
            if ordering != 'unordered' and self._fallback_repositories:
 
1912
                raise ValueError('unsupported ordering %r' % ordering)
 
1913
        iter_inv_fns = [self._iter_inventories_rpc] + [
 
1914
            fallback._iter_inventories for fallback in
 
1915
            self._fallback_repositories]
 
1916
        try:
 
1917
            for iter_inv in iter_inv_fns:
 
1918
                request = [revid for revid in revision_ids if revid in missing]
 
1919
                for inv, revid in iter_inv(request, ordering):
 
1920
                    if inv is None:
 
1921
                        continue
 
1922
                    missing.remove(inv.revision_id)
 
1923
                    if ordering != 'unordered':
 
1924
                        invs[revid] = inv
 
1925
                    else:
 
1926
                        yield inv, revid
 
1927
                if order_as_requested:
 
1928
                    # Yield as many results as we can while preserving order.
 
1929
                    while next_revid in invs:
 
1930
                        inv = invs.pop(next_revid)
 
1931
                        yield inv, inv.revision_id
 
1932
                        try:
 
1933
                            next_revid = order.pop()
 
1934
                        except IndexError:
 
1935
                            # We still want to fully consume the stream, just
 
1936
                            # in case it is not actually finished at this point
 
1937
                            next_revid = None
 
1938
                            break
 
1939
        except errors.UnknownSmartMethod:
 
1940
            for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
 
1941
                yield inv, revid
 
1942
            return
 
1943
        # Report missing
 
1944
        if order_as_requested:
 
1945
            if next_revid is not None:
 
1946
                yield None, next_revid
 
1947
            while order:
 
1948
                revid = order.pop()
 
1949
                yield invs.get(revid), revid
 
1950
        else:
 
1951
            while missing:
 
1952
                yield None, missing.pop()
1558
1953
 
1559
1954
    @needs_read_lock
1560
1955
    def get_revision(self, revision_id):
1561
 
        self._ensure_real()
1562
 
        return self._real_repository.get_revision(revision_id)
 
1956
        return self.get_revisions([revision_id])[0]
1563
1957
 
1564
1958
    def get_transaction(self):
1565
1959
        self._ensure_real()
1567
1961
 
1568
1962
    @needs_read_lock
1569
1963
    def clone(self, a_bzrdir, revision_id=None):
1570
 
        self._ensure_real()
1571
 
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
 
1964
        dest_repo = self._create_sprouting_repo(
 
1965
            a_bzrdir, shared=self.is_shared())
 
1966
        self.copy_content_into(dest_repo, revision_id)
 
1967
        return dest_repo
1572
1968
 
1573
1969
    def make_working_trees(self):
1574
1970
        """See Repository.make_working_trees"""
1575
 
        self._ensure_real()
1576
 
        return self._real_repository.make_working_trees()
 
1971
        path = self.bzrdir._path_for_remote_call(self._client)
 
1972
        try:
 
1973
            response = self._call('Repository.make_working_trees', path)
 
1974
        except errors.UnknownSmartMethod:
 
1975
            self._ensure_real()
 
1976
            return self._real_repository.make_working_trees()
 
1977
        if response[0] not in ('yes', 'no'):
 
1978
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
1979
        return response[0] == 'yes'
1577
1980
 
1578
1981
    def refresh_data(self):
1579
1982
        """Re-read any data needed to synchronise with disk.
1598
2001
        included_keys = result_set.intersection(result_parents)
1599
2002
        start_keys = result_set.difference(included_keys)
1600
2003
        exclude_keys = result_parents.difference(result_set)
1601
 
        result = graph.SearchResult(start_keys, exclude_keys,
 
2004
        result = vf_search.SearchResult(start_keys, exclude_keys,
1602
2005
            len(result_set), result_set)
1603
2006
        return result
1604
2007
 
1652
2055
        # the InterRepository base class, which raises an
1653
2056
        # IncompatibleRepositories when asked to fetch.
1654
2057
        inter = _mod_repository.InterRepository.get(source, self)
 
2058
        if (fetch_spec is not None and
 
2059
            not getattr(inter, "supports_fetch_spec", False)):
 
2060
            raise errors.UnsupportedOperation(
 
2061
                "fetch_spec not supported for %r" % inter)
1655
2062
        return inter.fetch(revision_id=revision_id,
1656
2063
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1657
2064
 
1675
2082
        return self._real_repository._get_versioned_file_checker(
1676
2083
            revisions, revision_versions_cache)
1677
2084
 
 
2085
    def _iter_files_bytes_rpc(self, desired_files, absent):
 
2086
        path = self.bzrdir._path_for_remote_call(self._client)
 
2087
        lines = []
 
2088
        identifiers = []
 
2089
        for (file_id, revid, identifier) in desired_files:
 
2090
            lines.append("%s\0%s" % (
 
2091
                osutils.safe_file_id(file_id),
 
2092
                osutils.safe_revision_id(revid)))
 
2093
            identifiers.append(identifier)
 
2094
        (response_tuple, response_handler) = (
 
2095
            self._call_with_body_bytes_expecting_body(
 
2096
            "Repository.iter_files_bytes", (path, ), "\n".join(lines)))
 
2097
        if response_tuple != ('ok', ):
 
2098
            response_handler.cancel_read_body()
 
2099
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2100
        byte_stream = response_handler.read_streamed_body()
 
2101
        def decompress_stream(start, byte_stream, unused):
 
2102
            decompressor = zlib.decompressobj()
 
2103
            yield decompressor.decompress(start)
 
2104
            while decompressor.unused_data == "":
 
2105
                try:
 
2106
                    data = byte_stream.next()
 
2107
                except StopIteration:
 
2108
                    break
 
2109
                yield decompressor.decompress(data)
 
2110
            yield decompressor.flush()
 
2111
            unused.append(decompressor.unused_data)
 
2112
        unused = ""
 
2113
        while True:
 
2114
            while not "\n" in unused:
 
2115
                unused += byte_stream.next()
 
2116
            header, rest = unused.split("\n", 1)
 
2117
            args = header.split("\0")
 
2118
            if args[0] == "absent":
 
2119
                absent[identifiers[int(args[3])]] = (args[1], args[2])
 
2120
                unused = rest
 
2121
                continue
 
2122
            elif args[0] == "ok":
 
2123
                idx = int(args[1])
 
2124
            else:
 
2125
                raise errors.UnexpectedSmartServerResponse(args)
 
2126
            unused_chunks = []
 
2127
            yield (identifiers[idx],
 
2128
                decompress_stream(rest, byte_stream, unused_chunks))
 
2129
            unused = "".join(unused_chunks)
 
2130
 
1678
2131
    def iter_files_bytes(self, desired_files):
1679
2132
        """See Repository.iter_file_bytes.
1680
2133
        """
1681
 
        self._ensure_real()
1682
 
        return self._real_repository.iter_files_bytes(desired_files)
 
2134
        try:
 
2135
            absent = {}
 
2136
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
 
2137
                    desired_files, absent):
 
2138
                yield identifier, bytes_iterator
 
2139
            for fallback in self._fallback_repositories:
 
2140
                if not absent:
 
2141
                    break
 
2142
                desired_files = [(key[0], key[1], identifier) for
 
2143
                    (identifier, key) in absent.iteritems()]
 
2144
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
 
2145
                    del absent[identifier]
 
2146
                    yield identifier, bytes_iterator
 
2147
            if absent:
 
2148
                # There may be more missing items, but raise an exception
 
2149
                # for just one.
 
2150
                missing_identifier = absent.keys()[0]
 
2151
                missing_key = absent[missing_identifier]
 
2152
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
 
2153
                    file_id=missing_key[0])
 
2154
        except errors.UnknownSmartMethod:
 
2155
            self._ensure_real()
 
2156
            for (identifier, bytes_iterator) in (
 
2157
                self._real_repository.iter_files_bytes(desired_files)):
 
2158
                yield identifier, bytes_iterator
 
2159
 
 
2160
    def get_cached_parent_map(self, revision_ids):
 
2161
        """See bzrlib.CachingParentsProvider.get_cached_parent_map"""
 
2162
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
1683
2163
 
1684
2164
    def get_parent_map(self, revision_ids):
1685
2165
        """See bzrlib.Graph.get_parent_map()."""
1744
2224
        if parents_map is None:
1745
2225
            # Repository is not locked, so there's no cache.
1746
2226
            parents_map = {}
1747
 
        # start_set is all the keys in the cache
1748
 
        start_set = set(parents_map)
1749
 
        # result set is all the references to keys in the cache
1750
 
        result_parents = set()
1751
 
        for parents in parents_map.itervalues():
1752
 
            result_parents.update(parents)
1753
 
        stop_keys = result_parents.difference(start_set)
1754
 
        # We don't need to send ghosts back to the server as a position to
1755
 
        # stop either.
1756
 
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
1757
 
        key_count = len(parents_map)
1758
 
        if (NULL_REVISION in result_parents
1759
 
            and NULL_REVISION in self._unstacked_provider.missing_keys):
1760
 
            # If we pruned NULL_REVISION from the stop_keys because it's also
1761
 
            # in our cache of "missing" keys we need to increment our key count
1762
 
            # by 1, because the reconsitituted SearchResult on the server will
1763
 
            # still consider NULL_REVISION to be an included key.
1764
 
            key_count += 1
1765
 
        included_keys = start_set.intersection(result_parents)
1766
 
        start_set.difference_update(included_keys)
 
2227
        if _DEFAULT_SEARCH_DEPTH <= 0:
 
2228
            (start_set, stop_keys,
 
2229
             key_count) = vf_search.search_result_from_parent_map(
 
2230
                parents_map, self._unstacked_provider.missing_keys)
 
2231
        else:
 
2232
            (start_set, stop_keys,
 
2233
             key_count) = vf_search.limited_search_result_from_parent_map(
 
2234
                parents_map, self._unstacked_provider.missing_keys,
 
2235
                keys, depth=_DEFAULT_SEARCH_DEPTH)
1767
2236
        recipe = ('manual', start_set, stop_keys, key_count)
1768
2237
        body = self._serialise_search_recipe(recipe)
1769
2238
        path = self.bzrdir._path_for_remote_call(self._client)
1818
2287
 
1819
2288
    @needs_read_lock
1820
2289
    def get_signature_text(self, revision_id):
1821
 
        self._ensure_real()
1822
 
        return self._real_repository.get_signature_text(revision_id)
 
2290
        path = self.bzrdir._path_for_remote_call(self._client)
 
2291
        try:
 
2292
            response_tuple, response_handler = self._call_expecting_body(
 
2293
                'Repository.get_revision_signature_text', path, revision_id)
 
2294
        except errors.UnknownSmartMethod:
 
2295
            self._ensure_real()
 
2296
            return self._real_repository.get_signature_text(revision_id)
 
2297
        except errors.NoSuchRevision, err:
 
2298
            for fallback in self._fallback_repositories:
 
2299
                try:
 
2300
                    return fallback.get_signature_text(revision_id)
 
2301
                except errors.NoSuchRevision:
 
2302
                    pass
 
2303
            raise err
 
2304
        else:
 
2305
            if response_tuple[0] != 'ok':
 
2306
                raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2307
            return response_handler.read_body_bytes()
1823
2308
 
1824
2309
    @needs_read_lock
1825
2310
    def _get_inventory_xml(self, revision_id):
 
2311
        # This call is used by older working tree formats,
 
2312
        # which stored a serialized basis inventory.
1826
2313
        self._ensure_real()
1827
2314
        return self._real_repository._get_inventory_xml(revision_id)
1828
2315
 
 
2316
    @needs_write_lock
1829
2317
    def reconcile(self, other=None, thorough=False):
1830
 
        self._ensure_real()
1831
 
        return self._real_repository.reconcile(other=other, thorough=thorough)
 
2318
        from bzrlib.reconcile import RepoReconciler
 
2319
        path = self.bzrdir._path_for_remote_call(self._client)
 
2320
        try:
 
2321
            response, handler = self._call_expecting_body(
 
2322
                'Repository.reconcile', path, self._lock_token)
 
2323
        except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
 
2324
            self._ensure_real()
 
2325
            return self._real_repository.reconcile(other=other, thorough=thorough)
 
2326
        if response != ('ok', ):
 
2327
            raise errors.UnexpectedSmartServerResponse(response)
 
2328
        body = handler.read_body_bytes()
 
2329
        result = RepoReconciler(self)
 
2330
        for line in body.split('\n'):
 
2331
            if not line:
 
2332
                continue
 
2333
            key, val_text = line.split(':')
 
2334
            if key == "garbage_inventories":
 
2335
                result.garbage_inventories = int(val_text)
 
2336
            elif key == "inconsistent_parents":
 
2337
                result.inconsistent_parents = int(val_text)
 
2338
            else:
 
2339
                mutter("unknown reconcile key %r" % key)
 
2340
        return result
1832
2341
 
1833
2342
    def all_revision_ids(self):
1834
 
        self._ensure_real()
1835
 
        return self._real_repository.all_revision_ids()
 
2343
        path = self.bzrdir._path_for_remote_call(self._client)
 
2344
        try:
 
2345
            response_tuple, response_handler = self._call_expecting_body(
 
2346
                "Repository.all_revision_ids", path)
 
2347
        except errors.UnknownSmartMethod:
 
2348
            self._ensure_real()
 
2349
            return self._real_repository.all_revision_ids()
 
2350
        if response_tuple != ("ok", ):
 
2351
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2352
        revids = set(response_handler.read_body_bytes().splitlines())
 
2353
        for fallback in self._fallback_repositories:
 
2354
            revids.update(set(fallback.all_revision_ids()))
 
2355
        return list(revids)
 
2356
 
 
2357
    def _filtered_revision_trees(self, revision_ids, file_ids):
 
2358
        """Return Tree for a revision on this branch with only some files.
 
2359
 
 
2360
        :param revision_ids: a sequence of revision-ids;
 
2361
          a revision-id may not be None or 'null:'
 
2362
        :param file_ids: if not None, the result is filtered
 
2363
          so that only those file-ids, their parents and their
 
2364
          children are included.
 
2365
        """
 
2366
        inventories = self.iter_inventories(revision_ids)
 
2367
        for inv in inventories:
 
2368
            # Should we introduce a FilteredRevisionTree class rather
 
2369
            # than pre-filter the inventory here?
 
2370
            filtered_inv = inv.filter(file_ids)
 
2371
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
1836
2372
 
1837
2373
    @needs_read_lock
1838
2374
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1839
 
        self._ensure_real()
1840
 
        return self._real_repository.get_deltas_for_revisions(revisions,
1841
 
            specific_fileids=specific_fileids)
 
2375
        medium = self._client._medium
 
2376
        if medium._is_remote_before((1, 2)):
 
2377
            self._ensure_real()
 
2378
            for delta in self._real_repository.get_deltas_for_revisions(
 
2379
                    revisions, specific_fileids):
 
2380
                yield delta
 
2381
            return
 
2382
        # Get the revision-ids of interest
 
2383
        required_trees = set()
 
2384
        for revision in revisions:
 
2385
            required_trees.add(revision.revision_id)
 
2386
            required_trees.update(revision.parent_ids[:1])
 
2387
 
 
2388
        # Get the matching filtered trees. Note that it's more
 
2389
        # efficient to pass filtered trees to changes_from() rather
 
2390
        # than doing the filtering afterwards. changes_from() could
 
2391
        # arguably do the filtering itself but it's path-based, not
 
2392
        # file-id based, so filtering before or afterwards is
 
2393
        # currently easier.
 
2394
        if specific_fileids is None:
 
2395
            trees = dict((t.get_revision_id(), t) for
 
2396
                t in self.revision_trees(required_trees))
 
2397
        else:
 
2398
            trees = dict((t.get_revision_id(), t) for
 
2399
                t in self._filtered_revision_trees(required_trees,
 
2400
                specific_fileids))
 
2401
 
 
2402
        # Calculate the deltas
 
2403
        for revision in revisions:
 
2404
            if not revision.parent_ids:
 
2405
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
 
2406
            else:
 
2407
                old_tree = trees[revision.parent_ids[0]]
 
2408
            yield trees[revision.revision_id].changes_from(old_tree)
1842
2409
 
1843
2410
    @needs_read_lock
1844
2411
    def get_revision_delta(self, revision_id, specific_fileids=None):
1845
 
        self._ensure_real()
1846
 
        return self._real_repository.get_revision_delta(revision_id,
1847
 
            specific_fileids=specific_fileids)
 
2412
        r = self.get_revision(revision_id)
 
2413
        return list(self.get_deltas_for_revisions([r],
 
2414
            specific_fileids=specific_fileids))[0]
1848
2415
 
1849
2416
    @needs_read_lock
1850
2417
    def revision_trees(self, revision_ids):
1851
 
        self._ensure_real()
1852
 
        return self._real_repository.revision_trees(revision_ids)
 
2418
        inventories = self.iter_inventories(revision_ids)
 
2419
        for inv in inventories:
 
2420
            yield InventoryRevisionTree(self, inv, inv.revision_id)
1853
2421
 
1854
2422
    @needs_read_lock
1855
2423
    def get_revision_reconcile(self, revision_id):
1863
2431
            callback_refs=callback_refs, check_repo=check_repo)
1864
2432
 
1865
2433
    def copy_content_into(self, destination, revision_id=None):
1866
 
        self._ensure_real()
1867
 
        return self._real_repository.copy_content_into(
1868
 
            destination, revision_id=revision_id)
 
2434
        """Make a complete copy of the content in self into destination.
 
2435
 
 
2436
        This is a destructive operation! Do not use it on existing
 
2437
        repositories.
 
2438
        """
 
2439
        interrepo = _mod_repository.InterRepository.get(self, destination)
 
2440
        return interrepo.copy_content(revision_id)
1869
2441
 
1870
2442
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1871
2443
        # get a tarball of the remote repository, and copy from that into the
1872
2444
        # destination
1873
 
        from bzrlib import osutils
1874
2445
        import tarfile
1875
2446
        # TODO: Maybe a progress bar while streaming the tarball?
1876
 
        note("Copying repository content as tarball...")
 
2447
        note(gettext("Copying repository content as tarball..."))
1877
2448
        tar_file = self._get_tarball('bz2')
1878
2449
        if tar_file is None:
1879
2450
            return None
1908
2479
    @needs_write_lock
1909
2480
    def pack(self, hint=None, clean_obsolete_packs=False):
1910
2481
        """Compress the data within the repository.
1911
 
 
1912
 
        This is not currently implemented within the smart server.
1913
2482
        """
1914
 
        self._ensure_real()
1915
 
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
 
2483
        if hint is None:
 
2484
            body = ""
 
2485
        else:
 
2486
            body = "".join([l+"\n" for l in hint])
 
2487
        path = self.bzrdir._path_for_remote_call(self._client)
 
2488
        try:
 
2489
            response, handler = self._call_with_body_bytes_expecting_body(
 
2490
                'Repository.pack', (path, self._lock_token,
 
2491
                    str(clean_obsolete_packs)), body)
 
2492
        except errors.UnknownSmartMethod:
 
2493
            self._ensure_real()
 
2494
            return self._real_repository.pack(hint=hint,
 
2495
                clean_obsolete_packs=clean_obsolete_packs)
 
2496
        handler.cancel_read_body()
 
2497
        if response != ('ok', ):
 
2498
            raise errors.UnexpectedSmartServerResponse(response)
1916
2499
 
1917
2500
    @property
1918
2501
    def revisions(self):
1919
2502
        """Decorate the real repository for now.
1920
2503
 
1921
 
        In the short term this should become a real object to intercept graph
1922
 
        lookups.
1923
 
 
1924
2504
        In the long term a full blown network facility is needed.
1925
2505
        """
1926
2506
        self._ensure_real()
1954
2534
 
1955
2535
    @needs_write_lock
1956
2536
    def sign_revision(self, revision_id, gpg_strategy):
1957
 
        self._ensure_real()
1958
 
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
 
2537
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2538
        plaintext = testament.as_short_text()
 
2539
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1959
2540
 
1960
2541
    @property
1961
2542
    def texts(self):
1967
2548
        self._ensure_real()
1968
2549
        return self._real_repository.texts
1969
2550
 
 
2551
    def _iter_revisions_rpc(self, revision_ids):
 
2552
        body = "\n".join(revision_ids)
 
2553
        path = self.bzrdir._path_for_remote_call(self._client)
 
2554
        response_tuple, response_handler = (
 
2555
            self._call_with_body_bytes_expecting_body(
 
2556
            "Repository.iter_revisions", (path, ), body))
 
2557
        if response_tuple[0] != "ok":
 
2558
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
2559
        serializer_format = response_tuple[1]
 
2560
        serializer = serializer_format_registry.get(serializer_format)
 
2561
        byte_stream = response_handler.read_streamed_body()
 
2562
        decompressor = zlib.decompressobj()
 
2563
        chunks = []
 
2564
        for bytes in byte_stream:
 
2565
            chunks.append(decompressor.decompress(bytes))
 
2566
            if decompressor.unused_data != "":
 
2567
                chunks.append(decompressor.flush())
 
2568
                yield serializer.read_revision_from_string("".join(chunks))
 
2569
                unused = decompressor.unused_data
 
2570
                decompressor = zlib.decompressobj()
 
2571
                chunks = [decompressor.decompress(unused)]
 
2572
        chunks.append(decompressor.flush())
 
2573
        text = "".join(chunks)
 
2574
        if text != "":
 
2575
            yield serializer.read_revision_from_string("".join(chunks))
 
2576
 
1970
2577
    @needs_read_lock
1971
2578
    def get_revisions(self, revision_ids):
1972
 
        self._ensure_real()
1973
 
        return self._real_repository.get_revisions(revision_ids)
 
2579
        if revision_ids is None:
 
2580
            revision_ids = self.all_revision_ids()
 
2581
        else:
 
2582
            for rev_id in revision_ids:
 
2583
                if not rev_id or not isinstance(rev_id, basestring):
 
2584
                    raise errors.InvalidRevisionId(
 
2585
                        revision_id=rev_id, branch=self)
 
2586
        try:
 
2587
            missing = set(revision_ids)
 
2588
            revs = {}
 
2589
            for rev in self._iter_revisions_rpc(revision_ids):
 
2590
                missing.remove(rev.revision_id)
 
2591
                revs[rev.revision_id] = rev
 
2592
        except errors.UnknownSmartMethod:
 
2593
            self._ensure_real()
 
2594
            return self._real_repository.get_revisions(revision_ids)
 
2595
        for fallback in self._fallback_repositories:
 
2596
            if not missing:
 
2597
                break
 
2598
            for revid in list(missing):
 
2599
                # XXX JRV 2011-11-20: It would be nice if there was a
 
2600
                # public method on Repository that could be used to query
 
2601
                # for revision objects *without* failing completely if one
 
2602
                # was missing. There is VersionedFileRepository._iter_revisions,
 
2603
                # but unfortunately that's private and not provided by
 
2604
                # all repository implementations.
 
2605
                try:
 
2606
                    revs[revid] = fallback.get_revision(revid)
 
2607
                except errors.NoSuchRevision:
 
2608
                    pass
 
2609
                else:
 
2610
                    missing.remove(revid)
 
2611
        if missing:
 
2612
            raise errors.NoSuchRevision(self, list(missing)[0])
 
2613
        return [revs[revid] for revid in revision_ids]
1974
2614
 
1975
2615
    def supports_rich_root(self):
1976
2616
        return self._format.rich_root_data
1984
2624
    def _serializer(self):
1985
2625
        return self._format._serializer
1986
2626
 
 
2627
    @needs_write_lock
1987
2628
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1988
 
        self._ensure_real()
1989
 
        return self._real_repository.store_revision_signature(
1990
 
            gpg_strategy, plaintext, revision_id)
 
2629
        signature = gpg_strategy.sign(plaintext)
 
2630
        self.add_signature_text(revision_id, signature)
1991
2631
 
1992
2632
    def add_signature_text(self, revision_id, signature):
1993
 
        self._ensure_real()
1994
 
        return self._real_repository.add_signature_text(revision_id, signature)
 
2633
        if self._real_repository:
 
2634
            # If there is a real repository the write group will
 
2635
            # be in the real repository as well, so use that:
 
2636
            self._ensure_real()
 
2637
            return self._real_repository.add_signature_text(
 
2638
                revision_id, signature)
 
2639
        path = self.bzrdir._path_for_remote_call(self._client)
 
2640
        response, handler = self._call_with_body_bytes_expecting_body(
 
2641
            'Repository.add_signature_text', (path, self._lock_token,
 
2642
                revision_id) + tuple(self._write_group_tokens), signature)
 
2643
        handler.cancel_read_body()
 
2644
        self.refresh_data()
 
2645
        if response[0] != 'ok':
 
2646
            raise errors.UnexpectedSmartServerResponse(response)
 
2647
        self._write_group_tokens = response[1:]
1995
2648
 
1996
2649
    def has_signature_for_revision_id(self, revision_id):
1997
 
        self._ensure_real()
1998
 
        return self._real_repository.has_signature_for_revision_id(revision_id)
 
2650
        path = self.bzrdir._path_for_remote_call(self._client)
 
2651
        try:
 
2652
            response = self._call('Repository.has_signature_for_revision_id',
 
2653
                path, revision_id)
 
2654
        except errors.UnknownSmartMethod:
 
2655
            self._ensure_real()
 
2656
            return self._real_repository.has_signature_for_revision_id(
 
2657
                revision_id)
 
2658
        if response[0] not in ('yes', 'no'):
 
2659
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
2660
        if response[0] == 'yes':
 
2661
            return True
 
2662
        for fallback in self._fallback_repositories:
 
2663
            if fallback.has_signature_for_revision_id(revision_id):
 
2664
                return True
 
2665
        return False
 
2666
 
 
2667
    @needs_read_lock
 
2668
    def verify_revision_signature(self, revision_id, gpg_strategy):
 
2669
        if not self.has_signature_for_revision_id(revision_id):
 
2670
            return gpg.SIGNATURE_NOT_SIGNED, None
 
2671
        signature = self.get_signature_text(revision_id)
 
2672
 
 
2673
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
2674
        plaintext = testament.as_short_text()
 
2675
 
 
2676
        return gpg_strategy.verify(signature, plaintext)
1999
2677
 
2000
2678
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2001
2679
        self._ensure_real()
2191
2869
 
2192
2870
    def _real_stream(self, repo, search):
2193
2871
        """Get a stream for search from repo.
2194
 
        
2195
 
        This never called RemoteStreamSource.get_stream, and is a heler
2196
 
        for RemoteStreamSource._get_stream to allow getting a stream 
 
2872
 
 
2873
        This never called RemoteStreamSource.get_stream, and is a helper
 
2874
        for RemoteStreamSource._get_stream to allow getting a stream
2197
2875
        reliably whether fallback back because of old servers or trying
2198
2876
        to stream from a non-RemoteRepository (which the stacked support
2199
2877
        code will do).
2241
2919
            except errors.UnknownSmartMethod:
2242
2920
                medium._remember_remote_is_before(version)
2243
2921
            except errors.UnknownErrorFromSmartServer, e:
2244
 
                if isinstance(search, graph.EverythingResult):
 
2922
                if isinstance(search, vf_search.EverythingResult):
2245
2923
                    error_verb = e.error_from_smart_server.error_verb
2246
2924
                    if error_verb == 'BadSearch':
2247
2925
                        # Pre-2.4 servers don't support this sort of search.
2339
3017
 
2340
3018
    def _ensure_real(self):
2341
3019
        if self._custom_format is None:
2342
 
            self._custom_format = branch.network_format_registry.get(
2343
 
                self._network_name)
 
3020
            try:
 
3021
                self._custom_format = branch.network_format_registry.get(
 
3022
                    self._network_name)
 
3023
            except KeyError:
 
3024
                raise errors.UnknownFormatError(kind='branch',
 
3025
                    format=self._network_name)
2344
3026
 
2345
3027
    def get_format_description(self):
2346
3028
        self._ensure_real()
2353
3035
        return a_bzrdir.open_branch(name=name, 
2354
3036
            ignore_fallbacks=ignore_fallbacks)
2355
3037
 
2356
 
    def _vfs_initialize(self, a_bzrdir, name):
 
3038
    def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
2357
3039
        # Initialisation when using a local bzrdir object, or a non-vfs init
2358
3040
        # method is not available on the server.
2359
3041
        # self._custom_format is always set - the start of initialize ensures
2361
3043
        if isinstance(a_bzrdir, RemoteBzrDir):
2362
3044
            a_bzrdir._ensure_real()
2363
3045
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2364
 
                name)
 
3046
                name, append_revisions_only=append_revisions_only)
2365
3047
        else:
2366
3048
            # We assume the bzrdir is parameterised; it may not be.
2367
 
            result = self._custom_format.initialize(a_bzrdir, name)
 
3049
            result = self._custom_format.initialize(a_bzrdir, name,
 
3050
                append_revisions_only=append_revisions_only)
2368
3051
        if (isinstance(a_bzrdir, RemoteBzrDir) and
2369
3052
            not isinstance(result, RemoteBranch)):
2370
3053
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2371
3054
                                  name=name)
2372
3055
        return result
2373
3056
 
2374
 
    def initialize(self, a_bzrdir, name=None, repository=None):
 
3057
    def initialize(self, a_bzrdir, name=None, repository=None,
 
3058
                   append_revisions_only=None):
2375
3059
        # 1) get the network name to use.
2376
3060
        if self._custom_format:
2377
3061
            network_name = self._custom_format.network_name()
2383
3067
            network_name = reference_format.network_name()
2384
3068
        # Being asked to create on a non RemoteBzrDir:
2385
3069
        if not isinstance(a_bzrdir, RemoteBzrDir):
2386
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3070
            return self._vfs_initialize(a_bzrdir, name=name,
 
3071
                append_revisions_only=append_revisions_only)
2387
3072
        medium = a_bzrdir._client._medium
2388
3073
        if medium._is_remote_before((1, 13)):
2389
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3074
            return self._vfs_initialize(a_bzrdir, name=name,
 
3075
                append_revisions_only=append_revisions_only)
2390
3076
        # Creating on a remote bzr dir.
2391
3077
        # 2) try direct creation via RPC
2392
3078
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2399
3085
        except errors.UnknownSmartMethod:
2400
3086
            # Fallback - use vfs methods
2401
3087
            medium._remember_remote_is_before((1, 13))
2402
 
            return self._vfs_initialize(a_bzrdir, name=name)
 
3088
            return self._vfs_initialize(a_bzrdir, name=name,
 
3089
                    append_revisions_only=append_revisions_only)
2403
3090
        if response[0] != 'ok':
2404
3091
            raise errors.UnexpectedSmartServerResponse(response)
2405
3092
        # Turn the response into a RemoteRepository object.
2426
3113
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2427
3114
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2428
3115
            format=format, setup_stacking=False, name=name)
 
3116
        if append_revisions_only:
 
3117
            remote_branch.set_append_revisions_only(append_revisions_only)
2429
3118
        # XXX: We know this is a new branch, so it must have revno 0, revid
2430
3119
        # NULL_REVISION. Creating the branch locked would make this be unable
2431
3120
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2464
3153
                return True
2465
3154
        return False
2466
3155
 
 
3156
 
 
3157
class RemoteBranchStore(config.IniFileStore):
 
3158
    """Branch store which attempts to use HPSS calls to retrieve branch store.
 
3159
 
 
3160
    Note that this is specific to bzr-based formats.
 
3161
    """
 
3162
 
 
3163
    def __init__(self, branch):
 
3164
        super(RemoteBranchStore, self).__init__()
 
3165
        self.branch = branch
 
3166
        self.id = "branch"
 
3167
        self._real_store = None
 
3168
 
 
3169
    def lock_write(self, token=None):
 
3170
        return self.branch.lock_write(token)
 
3171
 
 
3172
    def unlock(self):
 
3173
        return self.branch.unlock()
 
3174
 
 
3175
    @needs_write_lock
 
3176
    def save(self):
 
3177
        # We need to be able to override the undecorated implementation
 
3178
        self.save_without_locking()
 
3179
 
 
3180
    def save_without_locking(self):
 
3181
        super(RemoteBranchStore, self).save()
 
3182
 
 
3183
    def external_url(self):
 
3184
        return self.branch.user_url
 
3185
 
 
3186
    def _load_content(self):
 
3187
        path = self.branch._remote_path()
 
3188
        try:
 
3189
            response, handler = self.branch._call_expecting_body(
 
3190
                'Branch.get_config_file', path)
 
3191
        except errors.UnknownSmartMethod:
 
3192
            self._ensure_real()
 
3193
            return self._real_store._load_content()
 
3194
        if len(response) and response[0] != 'ok':
 
3195
            raise errors.UnexpectedSmartServerResponse(response)
 
3196
        return handler.read_body_bytes()
 
3197
 
 
3198
    def _save_content(self, content):
 
3199
        path = self.branch._remote_path()
 
3200
        try:
 
3201
            response, handler = self.branch._call_with_body_bytes_expecting_body(
 
3202
                'Branch.put_config_file', (path,
 
3203
                    self.branch._lock_token, self.branch._repo_lock_token),
 
3204
                content)
 
3205
        except errors.UnknownSmartMethod:
 
3206
            self._ensure_real()
 
3207
            return self._real_store._save_content(content)
 
3208
        handler.cancel_read_body()
 
3209
        if response != ('ok', ):
 
3210
            raise errors.UnexpectedSmartServerResponse(response)
 
3211
 
 
3212
    def _ensure_real(self):
 
3213
        self.branch._ensure_real()
 
3214
        if self._real_store is None:
 
3215
            self._real_store = config.BranchStore(self.branch)
 
3216
 
 
3217
 
2467
3218
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2468
3219
    """Branch stored on a server accessed by HPSS RPC.
2469
3220
 
2471
3222
    """
2472
3223
 
2473
3224
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2474
 
        _client=None, format=None, setup_stacking=True, name=None):
 
3225
        _client=None, format=None, setup_stacking=True, name=None,
 
3226
        possible_transports=None):
2475
3227
        """Create a RemoteBranch instance.
2476
3228
 
2477
3229
        :param real_branch: An optional local implementation of the branch
2542
3294
            hook(self)
2543
3295
        self._is_stacked = False
2544
3296
        if setup_stacking:
2545
 
            self._setup_stacking()
 
3297
            self._setup_stacking(possible_transports)
2546
3298
 
2547
 
    def _setup_stacking(self):
 
3299
    def _setup_stacking(self, possible_transports):
2548
3300
        # configure stacking into the remote repository, by reading it from
2549
3301
        # the vfs branch.
2550
3302
        try:
2553
3305
            errors.UnstackableRepositoryFormat), e:
2554
3306
            return
2555
3307
        self._is_stacked = True
2556
 
        self._activate_fallback_location(fallback_url)
 
3308
        if possible_transports is None:
 
3309
            possible_transports = []
 
3310
        else:
 
3311
            possible_transports = list(possible_transports)
 
3312
        possible_transports.append(self.bzrdir.root_transport)
 
3313
        self._activate_fallback_location(fallback_url,
 
3314
            possible_transports=possible_transports)
2557
3315
 
2558
3316
    def _get_config(self):
2559
3317
        return RemoteBranchConfig(self)
2560
3318
 
 
3319
    def _get_config_store(self):
 
3320
        return RemoteBranchStore(self)
 
3321
 
2561
3322
    def _get_real_transport(self):
2562
3323
        # if we try vfs access, return the real branch's vfs transport
2563
3324
        self._ensure_real()
2626
3387
                self.bzrdir, self._client)
2627
3388
        return self._control_files
2628
3389
 
2629
 
    def _get_checkout_format(self):
2630
 
        self._ensure_real()
2631
 
        return self._real_branch._get_checkout_format()
2632
 
 
2633
3390
    def get_physical_lock_status(self):
2634
3391
        """See Branch.get_physical_lock_status()."""
2635
 
        # should be an API call to the server, as branches must be lockable.
2636
 
        self._ensure_real()
2637
 
        return self._real_branch.get_physical_lock_status()
 
3392
        try:
 
3393
            response = self._client.call('Branch.get_physical_lock_status',
 
3394
                self._remote_path())
 
3395
        except errors.UnknownSmartMethod:
 
3396
            self._ensure_real()
 
3397
            return self._real_branch.get_physical_lock_status()
 
3398
        if response[0] not in ('yes', 'no'):
 
3399
            raise errors.UnexpectedSmartServerResponse(response)
 
3400
        return (response[0] == 'yes')
2638
3401
 
2639
3402
    def get_stacked_on_url(self):
2640
3403
        """Get the URL this branch is stacked against.
2824
3587
            self.repository.unlock()
2825
3588
 
2826
3589
    def break_lock(self):
2827
 
        self._ensure_real()
2828
 
        return self._real_branch.break_lock()
 
3590
        try:
 
3591
            response = self._call(
 
3592
                'Branch.break_lock', self._remote_path())
 
3593
        except errors.UnknownSmartMethod:
 
3594
            self._ensure_real()
 
3595
            return self._real_branch.break_lock()
 
3596
        if response != ('ok',):
 
3597
            raise errors.UnexpectedSmartServerResponse(response)
2829
3598
 
2830
3599
    def leave_lock_in_place(self):
2831
3600
        if not self._lock_token:
3005
3774
        return self._lock_count >= 1
3006
3775
 
3007
3776
    @needs_read_lock
 
3777
    def revision_id_to_dotted_revno(self, revision_id):
 
3778
        """Given a revision id, return its dotted revno.
 
3779
 
 
3780
        :return: a tuple like (1,) or (400,1,3).
 
3781
        """
 
3782
        try:
 
3783
            response = self._call('Branch.revision_id_to_revno',
 
3784
                self._remote_path(), revision_id)
 
3785
        except errors.UnknownSmartMethod:
 
3786
            self._ensure_real()
 
3787
            return self._real_branch.revision_id_to_dotted_revno(revision_id)
 
3788
        if response[0] == 'ok':
 
3789
            return tuple([int(x) for x in response[1:]])
 
3790
        else:
 
3791
            raise errors.UnexpectedSmartServerResponse(response)
 
3792
 
 
3793
    @needs_read_lock
3008
3794
    def revision_id_to_revno(self, revision_id):
3009
 
        self._ensure_real()
3010
 
        return self._real_branch.revision_id_to_revno(revision_id)
 
3795
        """Given a revision id on the branch mainline, return its revno.
 
3796
 
 
3797
        :return: an integer
 
3798
        """
 
3799
        try:
 
3800
            response = self._call('Branch.revision_id_to_revno',
 
3801
                self._remote_path(), revision_id)
 
3802
        except errors.UnknownSmartMethod:
 
3803
            self._ensure_real()
 
3804
            return self._real_branch.revision_id_to_revno(revision_id)
 
3805
        if response[0] == 'ok':
 
3806
            if len(response) == 2:
 
3807
                return int(response[1])
 
3808
            raise NoSuchRevision(self, revision_id)
 
3809
        else:
 
3810
            raise errors.UnexpectedSmartServerResponse(response)
3011
3811
 
3012
3812
    @needs_write_lock
3013
3813
    def set_last_revision_info(self, revno, revision_id):
3242
4042
        return self._bzrdir._real_bzrdir
3243
4043
 
3244
4044
 
3245
 
 
3246
4045
def _extract_tar(tar, to_dir):
3247
4046
    """Extract all the contents of a tarfile object.
3248
4047
 
3252
4051
        tar.extract(tarinfo, to_dir)
3253
4052
 
3254
4053
 
 
4054
error_translators = registry.Registry()
 
4055
no_context_error_translators = registry.Registry()
 
4056
 
 
4057
 
3255
4058
def _translate_error(err, **context):
3256
4059
    """Translate an ErrorFromSmartServer into a more useful error.
3257
4060
 
3286
4089
                    'Missing key %r in context %r', key_err.args[0], context)
3287
4090
                raise err
3288
4091
 
3289
 
    if err.error_verb == 'NoSuchRevision':
3290
 
        raise NoSuchRevision(find('branch'), err.error_args[0])
3291
 
    elif err.error_verb == 'nosuchrevision':
3292
 
        raise NoSuchRevision(find('repository'), err.error_args[0])
3293
 
    elif err.error_verb == 'nobranch':
3294
 
        if len(err.error_args) >= 1:
3295
 
            extra = err.error_args[0]
3296
 
        else:
3297
 
            extra = None
3298
 
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3299
 
            detail=extra)
3300
 
    elif err.error_verb == 'norepository':
3301
 
        raise errors.NoRepositoryPresent(find('bzrdir'))
3302
 
    elif err.error_verb == 'UnlockableTransport':
3303
 
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
3304
 
    elif err.error_verb == 'TokenMismatch':
3305
 
        raise errors.TokenMismatch(find('token'), '(remote token)')
3306
 
    elif err.error_verb == 'Diverged':
3307
 
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
3308
 
    elif err.error_verb == 'NotStacked':
3309
 
        raise errors.NotStacked(branch=find('branch'))
3310
 
    elif err.error_verb == 'PermissionDenied':
3311
 
        path = get_path()
3312
 
        if len(err.error_args) >= 2:
3313
 
            extra = err.error_args[1]
3314
 
        else:
3315
 
            extra = None
3316
 
        raise errors.PermissionDenied(path, extra=extra)
3317
 
    elif err.error_verb == 'ReadError':
3318
 
        path = get_path()
3319
 
        raise errors.ReadError(path)
3320
 
    elif err.error_verb == 'NoSuchFile':
3321
 
        path = get_path()
3322
 
        raise errors.NoSuchFile(path)
3323
 
    _translate_error_without_context(err)
3324
 
 
3325
 
 
3326
 
def _translate_error_without_context(err):
3327
 
    """Translate any ErrorFromSmartServer values that don't require context"""
3328
 
    if err.error_verb == 'IncompatibleRepositories':
3329
 
        raise errors.IncompatibleRepositories(err.error_args[0],
3330
 
            err.error_args[1], err.error_args[2])
3331
 
    elif err.error_verb == 'LockContention':
3332
 
        raise errors.LockContention('(remote lock)')
3333
 
    elif err.error_verb == 'LockFailed':
3334
 
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
3335
 
    elif err.error_verb == 'TipChangeRejected':
3336
 
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3337
 
    elif err.error_verb == 'UnstackableBranchFormat':
3338
 
        raise errors.UnstackableBranchFormat(*err.error_args)
3339
 
    elif err.error_verb == 'UnstackableRepositoryFormat':
3340
 
        raise errors.UnstackableRepositoryFormat(*err.error_args)
3341
 
    elif err.error_verb == 'FileExists':
3342
 
        raise errors.FileExists(err.error_args[0])
3343
 
    elif err.error_verb == 'DirectoryNotEmpty':
3344
 
        raise errors.DirectoryNotEmpty(err.error_args[0])
3345
 
    elif err.error_verb == 'ShortReadvError':
3346
 
        args = err.error_args
3347
 
        raise errors.ShortReadvError(
3348
 
            args[0], int(args[1]), int(args[2]), int(args[3]))
3349
 
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
4092
    try:
 
4093
        translator = error_translators.get(err.error_verb)
 
4094
    except KeyError:
 
4095
        pass
 
4096
    else:
 
4097
        raise translator(err, find, get_path)
 
4098
    try:
 
4099
        translator = no_context_error_translators.get(err.error_verb)
 
4100
    except KeyError:
 
4101
        raise errors.UnknownErrorFromSmartServer(err)
 
4102
    else:
 
4103
        raise translator(err)
 
4104
 
 
4105
 
 
4106
error_translators.register('NoSuchRevision',
 
4107
    lambda err, find, get_path: NoSuchRevision(
 
4108
        find('branch'), err.error_args[0]))
 
4109
error_translators.register('nosuchrevision',
 
4110
    lambda err, find, get_path: NoSuchRevision(
 
4111
        find('repository'), err.error_args[0]))
 
4112
 
 
4113
def _translate_nobranch_error(err, find, get_path):
 
4114
    if len(err.error_args) >= 1:
 
4115
        extra = err.error_args[0]
 
4116
    else:
 
4117
        extra = None
 
4118
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
4119
        detail=extra)
 
4120
 
 
4121
error_translators.register('nobranch', _translate_nobranch_error)
 
4122
error_translators.register('norepository',
 
4123
    lambda err, find, get_path: errors.NoRepositoryPresent(
 
4124
        find('bzrdir')))
 
4125
error_translators.register('UnlockableTransport',
 
4126
    lambda err, find, get_path: errors.UnlockableTransport(
 
4127
        find('bzrdir').root_transport))
 
4128
error_translators.register('TokenMismatch',
 
4129
    lambda err, find, get_path: errors.TokenMismatch(
 
4130
        find('token'), '(remote token)'))
 
4131
error_translators.register('Diverged',
 
4132
    lambda err, find, get_path: errors.DivergedBranches(
 
4133
        find('branch'), find('other_branch')))
 
4134
error_translators.register('NotStacked',
 
4135
    lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
 
4136
 
 
4137
def _translate_PermissionDenied(err, find, get_path):
 
4138
    path = get_path()
 
4139
    if len(err.error_args) >= 2:
 
4140
        extra = err.error_args[1]
 
4141
    else:
 
4142
        extra = None
 
4143
    return errors.PermissionDenied(path, extra=extra)
 
4144
 
 
4145
error_translators.register('PermissionDenied', _translate_PermissionDenied)
 
4146
error_translators.register('ReadError',
 
4147
    lambda err, find, get_path: errors.ReadError(get_path()))
 
4148
error_translators.register('NoSuchFile',
 
4149
    lambda err, find, get_path: errors.NoSuchFile(get_path()))
 
4150
error_translators.register('TokenLockingNotSupported',
 
4151
    lambda err, find, get_path: errors.TokenLockingNotSupported(
 
4152
        find('repository')))
 
4153
error_translators.register('UnsuspendableWriteGroup',
 
4154
    lambda err, find, get_path: errors.UnsuspendableWriteGroup(
 
4155
        repository=find('repository')))
 
4156
error_translators.register('UnresumableWriteGroup',
 
4157
    lambda err, find, get_path: errors.UnresumableWriteGroup(
 
4158
        repository=find('repository'), write_groups=err.error_args[0],
 
4159
        reason=err.error_args[1]))
 
4160
no_context_error_translators.register('IncompatibleRepositories',
 
4161
    lambda err: errors.IncompatibleRepositories(
 
4162
        err.error_args[0], err.error_args[1], err.error_args[2]))
 
4163
no_context_error_translators.register('LockContention',
 
4164
    lambda err: errors.LockContention('(remote lock)'))
 
4165
no_context_error_translators.register('LockFailed',
 
4166
    lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
 
4167
no_context_error_translators.register('TipChangeRejected',
 
4168
    lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
 
4169
no_context_error_translators.register('UnstackableBranchFormat',
 
4170
    lambda err: errors.UnstackableBranchFormat(*err.error_args))
 
4171
no_context_error_translators.register('UnstackableRepositoryFormat',
 
4172
    lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
 
4173
no_context_error_translators.register('FileExists',
 
4174
    lambda err: errors.FileExists(err.error_args[0]))
 
4175
no_context_error_translators.register('DirectoryNotEmpty',
 
4176
    lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
 
4177
 
 
4178
def _translate_short_readv_error(err):
 
4179
    args = err.error_args
 
4180
    return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
 
4181
        int(args[3]))
 
4182
 
 
4183
no_context_error_translators.register('ShortReadvError',
 
4184
    _translate_short_readv_error)
 
4185
 
 
4186
def _translate_unicode_error(err):
3350
4187
        encoding = str(err.error_args[0]) # encoding must always be a string
3351
4188
        val = err.error_args[1]
3352
4189
        start = int(err.error_args[2])
3360
4197
            raise UnicodeDecodeError(encoding, val, start, end, reason)
3361
4198
        elif err.error_verb == 'UnicodeEncodeError':
3362
4199
            raise UnicodeEncodeError(encoding, val, start, end, reason)
3363
 
    elif err.error_verb == 'ReadOnlyError':
3364
 
        raise errors.TransportNotPossible('readonly transport')
3365
 
    elif err.error_verb == 'MemoryError':
3366
 
        raise errors.BzrError("remote server out of memory\n"
3367
 
            "Retry non-remotely, or contact the server admin for details.")
3368
 
    raise errors.UnknownErrorFromSmartServer(err)
 
4200
 
 
4201
no_context_error_translators.register('UnicodeEncodeError',
 
4202
    _translate_unicode_error)
 
4203
no_context_error_translators.register('UnicodeDecodeError',
 
4204
    _translate_unicode_error)
 
4205
no_context_error_translators.register('ReadOnlyError',
 
4206
    lambda err: errors.TransportNotPossible('readonly transport'))
 
4207
no_context_error_translators.register('MemoryError',
 
4208
    lambda err: errors.BzrError("remote server out of memory\n"
 
4209
        "Retry non-remotely, or contact the server admin for details."))
 
4210
no_context_error_translators.register('RevisionNotPresent',
 
4211
    lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
 
4212
 
 
4213
no_context_error_translators.register('BzrCheckError',
 
4214
    lambda err: errors.BzrCheckError(msg=err.error_args[0]))
 
4215