17
17
# TODO: At some point, handle upgrades by just passing the whole request
18
18
# across to run on the server.
20
21
from cStringIO import StringIO
22
23
from bzrlib import (
29
from bzrlib.branch import Branch, BranchReferenceFormat
32
from bzrlib.branch import BranchReferenceFormat
30
33
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
31
34
from bzrlib.config import BranchConfig, TreeConfig
32
35
from bzrlib.decorators import needs_read_lock, needs_write_lock
33
36
from bzrlib.errors import NoSuchRevision
34
37
from bzrlib.lockable_files import LockableFiles
35
from bzrlib.pack import ContainerReader
38
from bzrlib.pack import ContainerPushParser
36
39
from bzrlib.smart import client, vfs
37
40
from bzrlib.symbol_versioning import (
41
from bzrlib.trace import note
44
from bzrlib.revision import NULL_REVISION
45
from bzrlib.trace import mutter, note
43
47
# Note: RemoteBzrDirFormat is in bzrdir.py
370
387
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
371
388
return response[0] == 'yes'
390
def has_revisions(self, revision_ids):
391
"""See Repository.has_revisions()."""
393
for revision_id in revision_ids:
394
if self.has_revision(revision_id):
395
result.add(revision_id)
373
398
def has_same_location(self, other):
374
399
return (self.__class__ == other.__class__ and
375
400
self.bzrdir.transport.base == other.bzrdir.transport.base)
377
402
def get_graph(self, other_repository=None):
378
403
"""Return the graph for this repository format"""
380
return self._real_repository.get_graph(other_repository)
404
parents_provider = self
405
if (other_repository is not None and
406
other_repository.bzrdir.transport.base !=
407
self.bzrdir.transport.base):
408
parents_provider = graph._StackedParentsProvider(
409
[parents_provider, other_repository._make_parents_provider()])
410
return graph.Graph(parents_provider)
382
412
def gather_stats(self, revid=None, committers=None):
383
413
"""See Repository.gather_stats()."""
654
703
"""RemoteRepositories never create working trees by default."""
706
def revision_ids_to_search_result(self, result_set):
707
"""Convert a set of revision ids to a graph SearchResult."""
708
result_parents = set()
709
for parents in self.get_graph().get_parent_map(
710
result_set).itervalues():
711
result_parents.update(parents)
712
included_keys = result_set.intersection(result_parents)
713
start_keys = result_set.difference(included_keys)
714
exclude_keys = result_parents.difference(result_set)
715
result = graph.SearchResult(start_keys, exclude_keys,
716
len(result_set), result_set)
720
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
721
"""Return the revision ids that other has that this does not.
723
These are returned in topological order.
725
revision_id: only return revision ids included by revision_id.
727
return repository.InterRepository.get(
728
other, self).search_missing_revision_ids(revision_id, find_ghosts)
657
730
def fetch(self, source, revision_id=None, pb=None):
658
731
if self.has_same_location(source):
659
732
# check that last_revision is in 'from' and then return a
700
773
self._ensure_real()
701
774
return self._real_repository.iter_files_bytes(desired_files)
776
def get_parent_map(self, keys):
777
"""See bzrlib.Graph.get_parent_map()."""
778
# Hack to build up the caching logic.
779
ancestry = self._parents_map
780
missing_revisions = set(key for key in keys if key not in ancestry)
781
if missing_revisions:
782
parent_map = self._get_parent_map(missing_revisions)
783
if 'hpss' in debug.debug_flags:
784
mutter('retransmitted revisions: %d of %d',
785
len(set(self._parents_map).intersection(parent_map)),
787
self._parents_map.update(parent_map)
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)
795
def _response_is_unknown_method(self, response, verb):
796
"""Return True if response is an unknonwn method response to verb.
798
:param response: The response from a smart client call_expecting_body
800
:param verb: The verb used in that call.
801
:return: True if an unknown method was encountered.
803
# This might live better on
804
# bzrlib.smart.protocol.SmartClientRequestProtocolOne
805
if (response[0] == ('error', "Generic bzr smart protocol error: "
806
"bad request '%s'" % verb) or
807
response[0] == ('error', "Generic bzr smart protocol error: "
808
"bad request u'%s'" % verb)):
809
response[1].cancel_read_body()
813
def _get_parent_map(self, keys):
814
"""Helper for get_parent_map that performs the RPC."""
816
if NULL_REVISION in keys:
817
keys.discard(NULL_REVISION)
818
found_parents = {NULL_REVISION:()}
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)
843
path = self.bzrdir._path_for_remote_call(self._client)
845
assert type(key) is str
846
verb = 'Repository.get_parent_map'
847
args = (path,) + tuple(keys)
848
response = self._client.call_with_body_bytes_expecting_body(
849
verb, args, self._serialise_search_recipe(recipe))
850
if self._response_is_unknown_method(response, verb):
851
# Server that does not support this method, get the whole graph.
852
response = self._client.call_expecting_body(
853
'Repository.get_revision_graph', path, '')
854
if response[0][0] not in ['ok', 'nosuchrevision']:
855
reponse[1].cancel_read_body()
856
raise errors.UnexpectedSmartServerResponse(response[0])
857
elif response[0][0] not in ['ok']:
858
reponse[1].cancel_read_body()
859
raise errors.UnexpectedSmartServerResponse(response[0])
860
if response[0][0] == 'ok':
861
coded = bz2.decompress(response[1].read_body_bytes())
865
lines = coded.split('\n')
868
d = tuple(line.split())
870
revision_graph[d[0]] = d[1:]
872
# No parents - so give the Graph result (NULL_REVISION,).
873
revision_graph[d[0]] = (NULL_REVISION,)
874
return revision_graph
704
877
def get_signature_text(self, revision_id):
705
878
self._ensure_real()
836
1008
self._ensure_real()
837
1009
return self._real_repository.has_signature_for_revision_id(revision_id)
839
def get_data_stream(self, revision_ids):
1011
def get_data_stream_for_search(self, search):
1012
REQUEST_NAME = 'Repository.stream_revisions_chunked'
840
1013
path = self.bzrdir._path_for_remote_call(self._client)
841
response, protocol = self._client.call_expecting_body(
842
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
1014
body = self._serialise_search_recipe(search.get_recipe())
1015
response, protocol = self._client.call_with_body_bytes_expecting_body(
1016
REQUEST_NAME, (path,), body)
843
1018
if response == ('ok',):
844
1019
return self._deserialise_stream(protocol)
1020
if response == ('NoSuchRevision', ):
1021
# We cannot easily identify the revision that is missing in this
1022
# situation without doing much more network IO. For now, bail.
1023
raise NoSuchRevision(self, "unknown")
845
1024
elif (response == ('error', "Generic bzr smart protocol error: "
846
"bad request 'Repository.stream_knit_data_for_revisions'") or
1025
"bad request '%s'" % REQUEST_NAME) or
847
1026
response == ('error', "Generic bzr smart protocol error: "
848
"bad request u'Repository.stream_knit_data_for_revisions'")):
1027
"bad request u'%s'" % REQUEST_NAME)):
849
1028
protocol.cancel_read_body()
850
1029
self._ensure_real()
851
return self._real_repository.get_data_stream(revision_ids)
1030
return self._real_repository.get_data_stream_for_search(search)
853
1032
raise errors.UnexpectedSmartServerResponse(response)
855
1034
def _deserialise_stream(self, protocol):
856
buffer = StringIO(protocol.read_body_bytes())
857
reader = ContainerReader(buffer)
858
for record_names, read_bytes in reader.iter_records():
860
# These records should have only one name, and that name
861
# should be a one-element tuple.
862
[name_tuple] = record_names
864
raise errors.SmartProtocolError(
865
'Repository data stream had invalid record name %r'
867
yield name_tuple, read_bytes(None)
1035
stream = protocol.read_streamed_body()
1036
container_parser = ContainerPushParser()
1037
for bytes in stream:
1038
container_parser.accept_bytes(bytes)
1039
records = container_parser.read_pending_records()
1040
for record_names, record_bytes in records:
1041
if len(record_names) != 1:
1042
# These records should have only one name, and that name
1043
# should be a one-element tuple.
1044
raise errors.SmartProtocolError(
1045
'Repository data stream had invalid record name %r'
1047
name_tuple = record_names[0]
1048
yield name_tuple, record_bytes
869
1050
def insert_data_stream(self, stream):
870
1051
self._ensure_real()