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
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)
158
176
raise errors.NoRepositoryPresent(self)
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)
797
# Repository is not locked, so there's no cache.
798
missing_revisions = set(keys)
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)),
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)
777
816
def _response_is_unknown_method(self, response, verb):
778
817
"""Return True if response is an unknonwn method response to verb.
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
841
return self.get_revision_graph()
798
844
if NULL_REVISION in keys:
799
845
keys.discard(NULL_REVISION)
802
848
return found_parents
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.
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.
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)
807
877
assert type(key) is str
808
878
verb = 'Repository.get_parent_map'
809
response = self._client.call_expecting_body(
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.
888
'Server is too old for fast get_parent_map, reconnecting. '
889
'(Upgrade the server to Bazaar 1.2 to avoid this)')
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())
824
901
# no revisions found
970
1047
return self._real_repository.has_signature_for_revision_id(revision_id)
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:
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)
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.
1066
'Server is too old for streaming pull, reconnecting. '
1067
'(Upgrade the server to Bazaar 1.2 to avoid this)')
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
1073
return self._real_repository.get_data_stream_for_search(search)
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()
995
return self._real_repository.get_data_stream_for_search(search)
997
1082
raise errors.UnexpectedSmartServerResponse(response)
1037
1122
def _make_parents_provider(self):
1125
def _serialise_search_recipe(self, recipe):
1126
"""Serialise a graph search recipe.
1128
:param recipe: A search recipe (start, stop, count).
1129
:return: Serialised bytes.
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))
1041
1137
class RemoteBranchLockableFiles(LockableFiles):
1042
1138
"""A 'LockableFiles' implementation that talks to a smart server.