764
777
"""See bzrlib.Graph.get_parent_map()."""
765
778
# Hack to build up the caching logic.
766
779
ancestry = self._parents_map
767
missing_revisions = set(key for key in keys if key not in ancestry)
781
# Repository is not locked, so there's no cache.
782
missing_revisions = set(keys)
785
missing_revisions = set(key for key in keys if key not in ancestry)
768
786
if missing_revisions:
769
787
parent_map = self._get_parent_map(missing_revisions)
770
788
if 'hpss' in debug.debug_flags:
771
789
mutter('retransmitted revisions: %d of %d',
772
len(set(self._parents_map).intersection(parent_map)),
790
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)
792
ancestry.update(parent_map)
793
present_keys = [k for k in keys if k in ancestry]
794
if 'hpss' in debug.debug_flags:
795
self._requested_parents.update(present_keys)
796
mutter('Current RemoteRepository graph hit rate: %d%%',
797
100.0 * len(self._requested_parents) / len(ancestry))
798
return dict((k, ancestry[k]) for k in present_keys)
777
800
def _response_is_unknown_method(self, response, verb):
778
801
"""Return True if response is an unknonwn method response to verb.
795
818
def _get_parent_map(self, keys):
796
819
"""Helper for get_parent_map that performs the RPC."""
820
medium = self._client.get_smart_medium()
821
if not medium._remote_is_at_least_1_2:
822
# We already found out that the server can't understand
823
# Repository.get_parent_map requests, so just fetch the whole
825
return self.get_revision_graph()
798
828
if NULL_REVISION in keys:
799
829
keys.discard(NULL_REVISION)
802
832
return found_parents
804
834
found_parents = {}
835
# TODO(Needs analysis): We could assume that the keys being requested
836
# from get_parent_map are in a breadth first search, so typically they
837
# will all be depth N from some common parent, and we don't have to
838
# have the server iterate from the root parent, but rather from the
839
# keys we're searching; and just tell the server the keyspace we
840
# already have; but this may be more traffic again.
842
# Transform self._parents_map into a search request recipe.
843
# TODO: Manage this incrementally to avoid covering the same path
844
# repeatedly. (The server will have to on each request, but the less
845
# work done the better).
846
parents_map = self._parents_map
847
if parents_map is None:
848
# Repository is not locked, so there's no cache.
850
start_set = set(parents_map)
851
result_parents = set()
852
for parents in parents_map.itervalues():
853
result_parents.update(parents)
854
stop_keys = result_parents.difference(start_set)
855
included_keys = start_set.intersection(result_parents)
856
start_set.difference_update(included_keys)
857
recipe = (start_set, stop_keys, len(parents_map))
858
body = self._serialise_search_recipe(recipe)
805
859
path = self.bzrdir._path_for_remote_call(self._client)
807
861
assert type(key) is str
808
862
verb = 'Repository.get_parent_map'
809
response = self._client.call_expecting_body(
863
args = (path,) + tuple(keys)
864
response = self._client.call_with_body_bytes_expecting_body(
865
verb, args, self._serialise_search_recipe(recipe))
811
866
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])
867
# Server does not support this method, so get the whole graph.
868
# Worse, we have to force a disconnection, because the server now
869
# doesn't realise it has a body on the wire to consume, so the
870
# only way to recover is to abandon the connection.
872
'Server is too old for fast get_parent_map, reconnecting. '
873
'(Upgrade the server to Bazaar 1.2 to avoid this)')
875
# To avoid having to disconnect repeatedly, we keep track of the
876
# fact the server doesn't understand remote methods added in 1.2.
877
medium._remote_is_at_least_1_2 = False
878
return self.get_revision_graph()
818
879
elif response[0][0] not in ['ok']:
819
880
reponse[1].cancel_read_body()
820
881
raise errors.UnexpectedSmartServerResponse(response[0])
821
882
if response[0][0] == 'ok':
822
coded = response[1].read_body_bytes()
883
coded = bz2.decompress(response[1].read_body_bytes())
824
885
# no revisions found
970
1031
return self._real_repository.has_signature_for_revision_id(revision_id)
972
1033
def get_data_stream_for_search(self, search):
1034
medium = self._client.get_smart_medium()
1035
if not medium._remote_is_at_least_1_2:
1037
return self._real_repository.get_data_stream_for_search(search)
973
1038
REQUEST_NAME = 'Repository.stream_revisions_chunked'
974
1039
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))
1040
body = self._serialise_search_recipe(search.get_recipe())
980
1041
response, protocol = self._client.call_with_body_bytes_expecting_body(
981
1042
REQUEST_NAME, (path,), body)
1044
if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
1045
# Server does not support this method, so fall back to VFS.
1046
# Worse, we have to force a disconnection, because the server now
1047
# doesn't realise it has a body on the wire to consume, so the
1048
# only way to recover is to abandon the connection.
1050
'Server is too old for streaming pull, reconnecting. '
1051
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1053
# To avoid having to disconnect repeatedly, we keep track of the
1054
# fact the server doesn't understand this remote method.
1055
medium._remote_is_at_least_1_2 = False
1057
return self._real_repository.get_data_stream_for_search(search)
983
1059
if response == ('ok',):
984
1060
return self._deserialise_stream(protocol)
985
1061
if response == ('NoSuchRevision', ):
986
1062
# We cannot easily identify the revision that is missing in this
987
1063
# situation without doing much more network IO. For now, bail.
988
1064
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
1066
raise errors.UnexpectedSmartServerResponse(response)
1037
1106
def _make_parents_provider(self):
1109
def _serialise_search_recipe(self, recipe):
1110
"""Serialise a graph search recipe.
1112
:param recipe: A search recipe (start, stop, count).
1113
:return: Serialised bytes.
1115
start_keys = ' '.join(recipe[0])
1116
stop_keys = ' '.join(recipe[1])
1117
count = str(recipe[2])
1118
return '\n'.join((start_keys, stop_keys, count))
1041
1121
class RemoteBranchLockableFiles(LockableFiles):
1042
1122
"""A 'LockableFiles' implementation that talks to a smart server.