/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Alexander Belchenko
  • Date: 2008-02-20 10:36:15 UTC
  • mfrom: (3228 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3231.
  • Revision ID: bialix@ukr.net-20080220103615-uxw9jc9m6l63ea27
merge bzr.dev; update patch for 1.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# TODO: At some point, handle upgrades by just passing the whole request
18
18
# across to run on the server.
19
19
 
 
20
import bz2
20
21
from cStringIO import StringIO
21
22
 
22
23
from bzrlib import (
41
42
    zero_ninetyone,
42
43
    )
43
44
from bzrlib.revision import NULL_REVISION
44
 
from bzrlib.trace import mutter, note
 
45
from bzrlib.trace import mutter, note, warning
45
46
 
46
47
# Note: RemoteBzrDirFormat is in bzrdir.py
47
48
 
130
131
        else:
131
132
            raise errors.UnexpectedSmartServerResponse(response)
132
133
 
 
134
    def _get_tree_branch(self):
 
135
        """See BzrDir._get_tree_branch()."""
 
136
        return None, self.open_branch()
 
137
 
133
138
    def open_branch(self, _unsupported=False):
134
139
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
135
140
        reference_url = self.get_branch_reference()
143
148
                
144
149
    def open_repository(self):
145
150
        path = self._path_for_remote_call(self._client)
146
 
        response = self._client.call('BzrDir.find_repository', path)
 
151
        verb = 'BzrDir.find_repositoryV2'
 
152
        response = self._client.call(verb, path)
 
153
        if (response == ('error', "Generic bzr smart protocol error: "
 
154
                "bad request '%s'" % verb) or
 
155
              response == ('error', "Generic bzr smart protocol error: "
 
156
                "bad request u'%s'" % verb)):
 
157
            verb = 'BzrDir.find_repository'
 
158
            response = self._client.call(verb, path)
147
159
        assert response[0] in ('ok', 'norepository'), \
148
160
            'unexpected response code %s' % (response,)
149
161
        if response[0] == 'norepository':
150
162
            raise errors.NoRepositoryPresent(self)
151
 
        assert len(response) == 4, 'incorrect response length %s' % (response,)
 
163
        if verb == 'BzrDir.find_repository':
 
164
            # servers that don't support the V2 method don't support external
 
165
            # references either.
 
166
            response = response + ('no', )
 
167
        assert len(response) == 5, 'incorrect response length %s' % (response,)
152
168
        if response[1] == '':
153
169
            format = RemoteRepositoryFormat()
154
170
            format.rich_root_data = (response[2] == 'yes')
155
171
            format.supports_tree_reference = (response[3] == 'yes')
 
172
            # No wire format to check this yet.
 
173
            format.supports_external_lookups = (response[4] == 'yes')
156
174
            return RemoteRepository(self, format)
157
175
        else:
158
176
            raise errors.NoRepositoryPresent(self)
268
286
        self._leave_lock = False
269
287
        # A cache of looked up revision parent data; reset at unlock time.
270
288
        self._parents_map = None
 
289
        if 'hpss' in debug.debug_flags:
 
290
            self._requested_parents = None
271
291
        # For tests:
272
292
        # These depend on the actual remote format, so force them off for
273
293
        # maximum compatibility. XXX: In future these should depend on the
277
297
        self._reconcile_fixes_text_parents = False
278
298
        self._reconcile_backsup_inventory = False
279
299
        self.base = self.bzrdir.transport.base
 
300
        # Can this repository be given external locations to lookup additional
 
301
        # data.
 
302
        self.supports_external_lookups = False
280
303
 
281
304
    def __str__(self):
282
305
        return "%s(%s)" % (self.__class__.__name__, self.base)
472
495
            self._lock_mode = 'r'
473
496
            self._lock_count = 1
474
497
            self._parents_map = {}
 
498
            if 'hpss' in debug.debug_flags:
 
499
                self._requested_parents = set()
475
500
            if self._real_repository is not None:
476
501
                self._real_repository.lock_read()
477
502
        else:
510
535
            self._lock_mode = 'w'
511
536
            self._lock_count = 1
512
537
            self._parents_map = {}
 
538
            if 'hpss' in debug.debug_flags:
 
539
                self._requested_parents = set()
513
540
        elif self._lock_mode == 'r':
514
541
            raise errors.ReadOnlyError(self)
515
542
        else:
570
597
        if self._lock_count > 0:
571
598
            return
572
599
        self._parents_map = None
 
600
        if 'hpss' in debug.debug_flags:
 
601
            self._requested_parents = None
573
602
        old_mode = self._lock_mode
574
603
        self._lock_mode = None
575
604
        try:
764
793
        """See bzrlib.Graph.get_parent_map()."""
765
794
        # Hack to build up the caching logic.
766
795
        ancestry = self._parents_map
767
 
        missing_revisions = set(key for key in keys if key not in ancestry)
 
796
        if ancestry is None:
 
797
            # Repository is not locked, so there's no cache.
 
798
            missing_revisions = set(keys)
 
799
            ancestry = {}
 
800
        else:
 
801
            missing_revisions = set(key for key in keys if key not in ancestry)
768
802
        if missing_revisions:
769
803
            parent_map = self._get_parent_map(missing_revisions)
770
804
            if 'hpss' in debug.debug_flags:
771
805
                mutter('retransmitted revisions: %d of %d',
772
 
                        len(set(self._parents_map).intersection(parent_map)),
 
806
                        len(set(ancestry).intersection(parent_map)),
773
807
                        len(parent_map))
774
 
            self._parents_map.update(parent_map)
775
 
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
 
808
            ancestry.update(parent_map)
 
809
        present_keys = [k for k in keys if k in ancestry]
 
810
        if 'hpss' in debug.debug_flags:
 
811
            self._requested_parents.update(present_keys)
 
812
            mutter('Current RemoteRepository graph hit rate: %d%%',
 
813
                100.0 * len(self._requested_parents) / len(ancestry))
 
814
        return dict((k, ancestry[k]) for k in present_keys)
776
815
 
777
816
    def _response_is_unknown_method(self, response, verb):
778
817
        """Return True if response is an unknonwn method response to verb.
794
833
 
795
834
    def _get_parent_map(self, keys):
796
835
        """Helper for get_parent_map that performs the RPC."""
 
836
        medium = self._client.get_smart_medium()
 
837
        if not medium._remote_is_at_least_1_2:
 
838
            # We already found out that the server can't understand
 
839
            # Repository.get_parent_map requests, so just fetch the whole
 
840
            # graph.
 
841
            return self.get_revision_graph()
 
842
 
797
843
        keys = set(keys)
798
844
        if NULL_REVISION in keys:
799
845
            keys.discard(NULL_REVISION)
802
848
                return found_parents
803
849
        else:
804
850
            found_parents = {}
 
851
        # TODO(Needs analysis): We could assume that the keys being requested
 
852
        # from get_parent_map are in a breadth first search, so typically they
 
853
        # will all be depth N from some common parent, and we don't have to
 
854
        # have the server iterate from the root parent, but rather from the
 
855
        # keys we're searching; and just tell the server the keyspace we
 
856
        # already have; but this may be more traffic again.
 
857
 
 
858
        # Transform self._parents_map into a search request recipe.
 
859
        # TODO: Manage this incrementally to avoid covering the same path
 
860
        # repeatedly. (The server will have to on each request, but the less
 
861
        # work done the better).
 
862
        parents_map = self._parents_map
 
863
        if parents_map is None:
 
864
            # Repository is not locked, so there's no cache.
 
865
            parents_map = {}
 
866
        start_set = set(parents_map)
 
867
        result_parents = set()
 
868
        for parents in parents_map.itervalues():
 
869
            result_parents.update(parents)
 
870
        stop_keys = result_parents.difference(start_set)
 
871
        included_keys = start_set.intersection(result_parents)
 
872
        start_set.difference_update(included_keys)
 
873
        recipe = (start_set, stop_keys, len(parents_map))
 
874
        body = self._serialise_search_recipe(recipe)
805
875
        path = self.bzrdir._path_for_remote_call(self._client)
806
876
        for key in keys:
807
877
            assert type(key) is str
808
878
        verb = 'Repository.get_parent_map'
809
 
        response = self._client.call_expecting_body(
810
 
            verb, path, *keys)
 
879
        args = (path,) + tuple(keys)
 
880
        response = self._client.call_with_body_bytes_expecting_body(
 
881
            verb, args, self._serialise_search_recipe(recipe))
811
882
        if self._response_is_unknown_method(response, verb):
812
 
            # Server that does not support this method, get the whole graph.
813
 
            response = self._client.call_expecting_body(
814
 
                'Repository.get_revision_graph', path, '')
815
 
            if response[0][0] not in ['ok', 'nosuchrevision']:
816
 
                reponse[1].cancel_read_body()
817
 
                raise errors.UnexpectedSmartServerResponse(response[0])
 
883
            # Server does not support this method, so get the whole graph.
 
884
            # Worse, we have to force a disconnection, because the server now
 
885
            # doesn't realise it has a body on the wire to consume, so the
 
886
            # only way to recover is to abandon the connection.
 
887
            warning(
 
888
                'Server is too old for fast get_parent_map, reconnecting.  '
 
889
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
890
            medium.disconnect()
 
891
            # To avoid having to disconnect repeatedly, we keep track of the
 
892
            # fact the server doesn't understand remote methods added in 1.2.
 
893
            medium._remote_is_at_least_1_2 = False
 
894
            return self.get_revision_graph()
818
895
        elif response[0][0] not in ['ok']:
819
896
            reponse[1].cancel_read_body()
820
897
            raise errors.UnexpectedSmartServerResponse(response[0])
821
898
        if response[0][0] == 'ok':
822
 
            coded = response[1].read_body_bytes()
 
899
            coded = bz2.decompress(response[1].read_body_bytes())
823
900
            if coded == '':
824
901
                # no revisions found
825
902
                return {}
970
1047
        return self._real_repository.has_signature_for_revision_id(revision_id)
971
1048
 
972
1049
    def get_data_stream_for_search(self, search):
 
1050
        medium = self._client.get_smart_medium()
 
1051
        if not medium._remote_is_at_least_1_2:
 
1052
            self._ensure_real()
 
1053
            return self._real_repository.get_data_stream_for_search(search)
973
1054
        REQUEST_NAME = 'Repository.stream_revisions_chunked'
974
1055
        path = self.bzrdir._path_for_remote_call(self._client)
975
 
        recipe = search.get_recipe()
976
 
        start_keys = ' '.join(recipe[0])
977
 
        stop_keys = ' '.join(recipe[1])
978
 
        count = str(recipe[2])
979
 
        body = '\n'.join((start_keys, stop_keys, count))
 
1056
        body = self._serialise_search_recipe(search.get_recipe())
980
1057
        response, protocol = self._client.call_with_body_bytes_expecting_body(
981
1058
            REQUEST_NAME, (path,), body)
982
1059
 
 
1060
        if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
 
1061
            # Server does not support this method, so fall back to VFS.
 
1062
            # Worse, we have to force a disconnection, because the server now
 
1063
            # doesn't realise it has a body on the wire to consume, so the
 
1064
            # only way to recover is to abandon the connection.
 
1065
            warning(
 
1066
                'Server is too old for streaming pull, reconnecting.  '
 
1067
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
1068
            medium.disconnect()
 
1069
            # To avoid having to disconnect repeatedly, we keep track of the
 
1070
            # fact the server doesn't understand this remote method.
 
1071
            medium._remote_is_at_least_1_2 = False
 
1072
            self._ensure_real()
 
1073
            return self._real_repository.get_data_stream_for_search(search)
 
1074
 
983
1075
        if response == ('ok',):
984
1076
            return self._deserialise_stream(protocol)
985
1077
        if response == ('NoSuchRevision', ):
986
1078
            # We cannot easily identify the revision that is missing in this
987
1079
            # situation without doing much more network IO. For now, bail.
988
1080
            raise NoSuchRevision(self, "unknown")
989
 
        elif (response == ('error', "Generic bzr smart protocol error: "
990
 
                "bad request '%s'" % REQUEST_NAME) or
991
 
              response == ('error', "Generic bzr smart protocol error: "
992
 
                "bad request u'%s'" % REQUEST_NAME)):
993
 
            protocol.cancel_read_body()
994
 
            self._ensure_real()
995
 
            return self._real_repository.get_data_stream_for_search(search)
996
1081
        else:
997
1082
            raise errors.UnexpectedSmartServerResponse(response)
998
1083
 
1037
1122
    def _make_parents_provider(self):
1038
1123
        return self
1039
1124
 
 
1125
    def _serialise_search_recipe(self, recipe):
 
1126
        """Serialise a graph search recipe.
 
1127
 
 
1128
        :param recipe: A search recipe (start, stop, count).
 
1129
        :return: Serialised bytes.
 
1130
        """
 
1131
        start_keys = ' '.join(recipe[0])
 
1132
        stop_keys = ' '.join(recipe[1])
 
1133
        count = str(recipe[2])
 
1134
        return '\n'.join((start_keys, stop_keys, count))
 
1135
 
1040
1136
 
1041
1137
class RemoteBranchLockableFiles(LockableFiles):
1042
1138
    """A 'LockableFiles' implementation that talks to a smart server.