~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Alexander Belchenko
  • Date: 2008-02-16 10:03:17 UTC
  • mfrom: (3224 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3230.
  • Revision ID: bialix@ukr.net-20080216100317-xg1hdw306evlgt94
merge bzr.dev; update for 1.3; $BZR_LOG used in trace.py module (again), not in the main bzr script (req. by Robert Collins)

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()
268
273
        self._leave_lock = False
269
274
        # A cache of looked up revision parent data; reset at unlock time.
270
275
        self._parents_map = None
 
276
        if 'hpss' in debug.debug_flags:
 
277
            self._requested_parents = None
271
278
        # For tests:
272
279
        # These depend on the actual remote format, so force them off for
273
280
        # maximum compatibility. XXX: In future these should depend on the
472
479
            self._lock_mode = 'r'
473
480
            self._lock_count = 1
474
481
            self._parents_map = {}
 
482
            if 'hpss' in debug.debug_flags:
 
483
                self._requested_parents = set()
475
484
            if self._real_repository is not None:
476
485
                self._real_repository.lock_read()
477
486
        else:
510
519
            self._lock_mode = 'w'
511
520
            self._lock_count = 1
512
521
            self._parents_map = {}
 
522
            if 'hpss' in debug.debug_flags:
 
523
                self._requested_parents = set()
513
524
        elif self._lock_mode == 'r':
514
525
            raise errors.ReadOnlyError(self)
515
526
        else:
570
581
        if self._lock_count > 0:
571
582
            return
572
583
        self._parents_map = None
 
584
        if 'hpss' in debug.debug_flags:
 
585
            self._requested_parents = None
573
586
        old_mode = self._lock_mode
574
587
        self._lock_mode = None
575
588
        try:
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)
 
780
        if ancestry is None:
 
781
            # Repository is not locked, so there's no cache.
 
782
            missing_revisions = set(keys)
 
783
            ancestry = {}
 
784
        else:
 
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)),
773
791
                        len(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)
776
799
 
777
800
    def _response_is_unknown_method(self, response, verb):
778
801
        """Return True if response is an unknonwn method response to verb.
794
817
 
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
 
824
            # graph.
 
825
            return self.get_revision_graph()
 
826
 
797
827
        keys = set(keys)
798
828
        if NULL_REVISION in keys:
799
829
            keys.discard(NULL_REVISION)
802
832
                return found_parents
803
833
        else:
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.
 
841
 
 
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.
 
849
            parents_map = {}
 
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)
806
860
        for key in keys:
807
861
            assert type(key) is str
808
862
        verb = 'Repository.get_parent_map'
809
 
        response = self._client.call_expecting_body(
810
 
            verb, path, *keys)
 
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.
 
871
            warning(
 
872
                'Server is too old for fast get_parent_map, reconnecting.  '
 
873
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
874
            medium.disconnect()
 
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())
823
884
            if coded == '':
824
885
                # no revisions found
825
886
                return {}
970
1031
        return self._real_repository.has_signature_for_revision_id(revision_id)
971
1032
 
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:
 
1036
            self._ensure_real()
 
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)
982
1043
 
 
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.
 
1049
            warning(
 
1050
                'Server is too old for streaming pull, reconnecting.  '
 
1051
                '(Upgrade the server to Bazaar 1.2 to avoid this)')
 
1052
            medium.disconnect()
 
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
 
1056
            self._ensure_real()
 
1057
            return self._real_repository.get_data_stream_for_search(search)
 
1058
 
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()
994
 
            self._ensure_real()
995
 
            return self._real_repository.get_data_stream_for_search(search)
996
1065
        else:
997
1066
            raise errors.UnexpectedSmartServerResponse(response)
998
1067
 
1037
1106
    def _make_parents_provider(self):
1038
1107
        return self
1039
1108
 
 
1109
    def _serialise_search_recipe(self, recipe):
 
1110
        """Serialise a graph search recipe.
 
1111
 
 
1112
        :param recipe: A search recipe (start, stop, count).
 
1113
        :return: Serialised bytes.
 
1114
        """
 
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))
 
1119
 
1040
1120
 
1041
1121
class RemoteBranchLockableFiles(LockableFiles):
1042
1122
    """A 'LockableFiles' implementation that talks to a smart server.