272
273
self._leave_lock = False
273
274
# A cache of looked up revision parent data; reset at unlock time.
274
275
self._parents_map = None
276
if 'hpss' in debug.debug_flags:
277
self._requested_parents = None
276
279
# These depend on the actual remote format, so force them off for
277
280
# maximum compatibility. XXX: In future these should depend on the
776
785
len(set(self._parents_map).intersection(parent_map)),
778
787
self._parents_map.update(parent_map)
779
return dict((k, ancestry[k]) for k in keys if k in ancestry)
788
present_keys = [k for k in keys if k in ancestry]
789
if 'hpss' in debug.debug_flags:
790
self._requested_parents.update(present_keys)
791
mutter('Current RemoteRepository graph hit rate: %d%%',
792
100.0 * len(self._requested_parents) / len(self._parents_map))
793
return dict((k, ancestry[k]) for k in present_keys)
781
795
def _response_is_unknown_method(self, response, verb):
782
796
"""Return True if response is an unknonwn method response to verb.
806
820
return found_parents
808
822
found_parents = {}
823
# TODO(Needs analysis): We could assume that the keys being requested
824
# from get_parent_map are in a breadth first search, so typically they
825
# will all be depth N from some common parent, and we don't have to
826
# have the server iterate from the root parent, but rather from the
827
# keys we're searching; and just tell the server the keyspace we
828
# already have; but this may be more traffic again.
830
# Transform self._parents_map into a search request recipe.
831
# TODO: Manage this incrementally to avoid covering the same path
832
# repeatedly. (The server will have to on each request, but the less
833
# work done the better).
834
start_set = set(self._parents_map)
835
result_parents = set()
836
for parents in self._parents_map.itervalues():
837
result_parents.update(parents)
838
stop_keys = result_parents.difference(start_set)
839
included_keys = start_set.intersection(result_parents)
840
start_set.difference_update(included_keys)
841
recipe = (start_set, stop_keys, len(self._parents_map))
842
body = self._serialise_search_recipe(recipe)
809
843
path = self.bzrdir._path_for_remote_call(self._client)
811
845
assert type(key) is str
812
846
verb = 'Repository.get_parent_map'
813
response = self._client.call_expecting_body(
847
args = (path,) + tuple(keys)
848
response = self._client.call_with_body_bytes_expecting_body(
849
verb, args, self._serialise_search_recipe(recipe))
815
850
if self._response_is_unknown_method(response, verb):
816
851
# Server that does not support this method, get the whole graph.
817
852
response = self._client.call_expecting_body(
823
858
reponse[1].cancel_read_body()
824
859
raise errors.UnexpectedSmartServerResponse(response[0])
825
860
if response[0][0] == 'ok':
826
coded = response[1].read_body_bytes()
861
coded = bz2.decompress(response[1].read_body_bytes())
828
863
# no revisions found
976
1011
def get_data_stream_for_search(self, search):
977
1012
REQUEST_NAME = 'Repository.stream_revisions_chunked'
978
1013
path = self.bzrdir._path_for_remote_call(self._client)
979
recipe = search.get_recipe()
980
start_keys = ' '.join(recipe[0])
981
stop_keys = ' '.join(recipe[1])
982
count = str(recipe[2])
983
body = '\n'.join((start_keys, stop_keys, count))
1014
body = self._serialise_search_recipe(search.get_recipe())
984
1015
response, protocol = self._client.call_with_body_bytes_expecting_body(
985
1016
REQUEST_NAME, (path,), body)
1041
1072
def _make_parents_provider(self):
1075
def _serialise_search_recipe(self, recipe):
1076
"""Serialise a graph search recipe.
1078
:param recipe: A search recipe (start, stop, count).
1079
:return: Serialised bytes.
1081
start_keys = ' '.join(recipe[0])
1082
stop_keys = ' '.join(recipe[1])
1083
count = str(recipe[2])
1084
return '\n'.join((start_keys, stop_keys, count))
1045
1087
class RemoteBranchLockableFiles(LockableFiles):
1046
1088
"""A 'LockableFiles' implementation that talks to a smart server.