/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Robert Collins
  • Date: 2008-02-06 04:06:42 UTC
  • mfrom: (3216 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3217.
  • Revision ID: robertc@robertcollins.net-20080206040642-2efx3l4iv5f95lxp
Merge up with bzr.dev.

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 (
23
24
    branch,
 
25
    debug,
24
26
    errors,
 
27
    graph,
25
28
    lockdir,
26
29
    repository,
27
30
    revision,
28
31
)
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 (
38
41
    deprecated_method,
39
42
    zero_ninetyone,
40
43
    )
41
 
from bzrlib.trace import note
 
44
from bzrlib.revision import NULL_REVISION
 
45
from bzrlib.trace import mutter, note
42
46
 
43
47
# Note: RemoteBzrDirFormat is in bzrdir.py
44
48
 
85
89
        self._real_bzrdir.create_repository(shared=shared)
86
90
        return self.open_repository()
87
91
 
 
92
    def destroy_repository(self):
 
93
        """See BzrDir.destroy_repository"""
 
94
        self._ensure_real()
 
95
        self._real_bzrdir.destroy_repository()
 
96
 
88
97
    def create_branch(self):
89
98
        self._ensure_real()
90
99
        real_branch = self._real_bzrdir.create_branch()
122
131
        else:
123
132
            raise errors.UnexpectedSmartServerResponse(response)
124
133
 
 
134
    def _get_tree_branch(self):
 
135
        """See BzrDir._get_tree_branch()."""
 
136
        return None, self.open_branch()
 
137
 
125
138
    def open_branch(self, _unsupported=False):
126
139
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
127
140
        reference_url = self.get_branch_reference()
192
205
    Instances of this repository are represented by RemoteRepository
193
206
    instances.
194
207
 
195
 
    The RemoteRepositoryFormat is parameterised during construction
 
208
    The RemoteRepositoryFormat is parameterized during construction
196
209
    to reflect the capabilities of the real, remote format. Specifically
197
210
    the attributes rich_root_data and supports_tree_reference are set
198
211
    on a per instance basis, and are not set (and should not be) at
258
271
        self._lock_token = None
259
272
        self._lock_count = 0
260
273
        self._leave_lock = False
 
274
        # A cache of looked up revision parent data; reset at unlock time.
 
275
        self._parents_map = None
 
276
        if 'hpss' in debug.debug_flags:
 
277
            self._requested_parents = None
261
278
        # For tests:
262
279
        # These depend on the actual remote format, so force them off for
263
280
        # maximum compatibility. XXX: In future these should depend on the
362
379
 
363
380
    def has_revision(self, revision_id):
364
381
        """See Repository.has_revision()."""
365
 
        if revision_id is None:
 
382
        if revision_id == NULL_REVISION:
366
383
            # The null revision is always present.
367
384
            return True
368
385
        path = self.bzrdir._path_for_remote_call(self._client)
370
387
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
371
388
        return response[0] == 'yes'
372
389
 
 
390
    def has_revisions(self, revision_ids):
 
391
        """See Repository.has_revisions()."""
 
392
        result = set()
 
393
        for revision_id in revision_ids:
 
394
            if self.has_revision(revision_id):
 
395
                result.add(revision_id)
 
396
        return result
 
397
 
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)
376
401
        
377
402
    def get_graph(self, other_repository=None):
378
403
        """Return the graph for this repository format"""
379
 
        self._ensure_real()
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)
381
411
 
382
412
    def gather_stats(self, revid=None, committers=None):
383
413
        """See Repository.gather_stats()."""
410
440
 
411
441
        return result
412
442
 
 
443
    def find_branches(self, using=False):
 
444
        """See Repository.find_branches()."""
 
445
        # should be an API call to the server.
 
446
        self._ensure_real()
 
447
        return self._real_repository.find_branches(using=using)
 
448
 
413
449
    def get_physical_lock_status(self):
414
450
        """See Repository.get_physical_lock_status()."""
415
451
        # should be an API call to the server.
442
478
        if not self._lock_mode:
443
479
            self._lock_mode = 'r'
444
480
            self._lock_count = 1
 
481
            self._parents_map = {}
 
482
            if 'hpss' in debug.debug_flags:
 
483
                self._requested_parents = set()
445
484
            if self._real_repository is not None:
446
485
                self._real_repository.lock_read()
447
486
        else:
479
518
                self._leave_lock = False
480
519
            self._lock_mode = 'w'
481
520
            self._lock_count = 1
 
521
            self._parents_map = {}
 
522
            if 'hpss' in debug.debug_flags:
 
523
                self._requested_parents = set()
482
524
        elif self._lock_mode == 'r':
483
525
            raise errors.ReadOnlyError(self)
484
526
        else:
538
580
        self._lock_count -= 1
539
581
        if self._lock_count > 0:
540
582
            return
 
583
        self._parents_map = None
 
584
        if 'hpss' in debug.debug_flags:
 
585
            self._requested_parents = None
541
586
        old_mode = self._lock_mode
542
587
        self._lock_mode = None
543
588
        try:
590
635
 
591
636
    def sprout(self, to_bzrdir, revision_id=None):
592
637
        # TODO: Option to control what format is created?
593
 
        dest_repo = to_bzrdir.create_repository()
 
638
        self._ensure_real()
 
639
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
640
                                                             shared=False)
594
641
        dest_repo.fetch(self, revision_id=revision_id)
595
642
        return dest_repo
596
643
 
615
662
                committer=committer, revprops=revprops, revision_id=revision_id)
616
663
        return builder
617
664
 
618
 
    @needs_write_lock
619
665
    def add_inventory(self, revid, inv, parents):
620
666
        self._ensure_real()
621
667
        return self._real_repository.add_inventory(revid, inv, parents)
622
668
 
623
 
    @needs_write_lock
624
669
    def add_revision(self, rev_id, rev, inv=None, config=None):
625
670
        self._ensure_real()
626
671
        return self._real_repository.add_revision(
631
676
        self._ensure_real()
632
677
        return self._real_repository.get_inventory(revision_id)
633
678
 
 
679
    def iter_inventories(self, revision_ids):
 
680
        self._ensure_real()
 
681
        return self._real_repository.iter_inventories(revision_ids)
 
682
 
634
683
    @needs_read_lock
635
684
    def get_revision(self, revision_id):
636
685
        self._ensure_real()
654
703
        """RemoteRepositories never create working trees by default."""
655
704
        return False
656
705
 
 
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)
 
717
        return result
 
718
 
 
719
    @needs_read_lock
 
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.
 
722
        
 
723
        These are returned in topological order.
 
724
 
 
725
        revision_id: only return revision ids included by revision_id.
 
726
        """
 
727
        return repository.InterRepository.get(
 
728
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
 
729
 
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
689
762
        self._ensure_real()
690
763
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
691
764
 
692
 
    def get_versioned_file_checker(self, revisions, revision_versions_cache):
 
765
    def _get_versioned_file_checker(self, revisions, revision_versions_cache):
693
766
        self._ensure_real()
694
 
        return self._real_repository.get_versioned_file_checker(
 
767
        return self._real_repository._get_versioned_file_checker(
695
768
            revisions, revision_versions_cache)
696
769
        
697
770
    def iter_files_bytes(self, desired_files):
700
773
        self._ensure_real()
701
774
        return self._real_repository.iter_files_bytes(desired_files)
702
775
 
 
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)),
 
786
                        len(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)
 
794
 
 
795
    def _response_is_unknown_method(self, response, verb):
 
796
        """Return True if response is an unknonwn method response to verb.
 
797
        
 
798
        :param response: The response from a smart client call_expecting_body
 
799
            call.
 
800
        :param verb: The verb used in that call.
 
801
        :return: True if an unknown method was encountered.
 
802
        """
 
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()
 
810
           return True
 
811
        return False
 
812
 
 
813
    def _get_parent_map(self, keys):
 
814
        """Helper for get_parent_map that performs the RPC."""
 
815
        keys = set(keys)
 
816
        if NULL_REVISION in keys:
 
817
            keys.discard(NULL_REVISION)
 
818
            found_parents = {NULL_REVISION:()}
 
819
            if not keys:
 
820
                return found_parents
 
821
        else:
 
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.
 
829
 
 
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)
 
844
        for key in keys:
 
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())
 
862
            if coded == '':
 
863
                # no revisions found
 
864
                return {}
 
865
            lines = coded.split('\n')
 
866
            revision_graph = {}
 
867
            for line in lines:
 
868
                d = tuple(line.split())
 
869
                if len(d) > 1:
 
870
                    revision_graph[d[0]] = d[1:]
 
871
                else:
 
872
                    # No parents - so give the Graph result (NULL_REVISION,).
 
873
                    revision_graph[d[0]] = (NULL_REVISION,)
 
874
            return revision_graph
 
875
 
703
876
    @needs_read_lock
704
877
    def get_signature_text(self, revision_id):
705
878
        self._ensure_real()
764
937
        from bzrlib import osutils
765
938
        import tarfile
766
939
        import tempfile
767
 
        from StringIO import StringIO
768
940
        # TODO: Maybe a progress bar while streaming the tarball?
769
941
        note("Copying repository content as tarball...")
770
942
        tar_file = self._get_tarball('bz2')
836
1008
        self._ensure_real()
837
1009
        return self._real_repository.has_signature_for_revision_id(revision_id)
838
1010
 
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)
 
1017
 
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)
852
1031
        else:
853
1032
            raise errors.UnexpectedSmartServerResponse(response)
854
1033
 
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():
859
 
            try:
860
 
                # These records should have only one name, and that name
861
 
                # should be a one-element tuple.
862
 
                [name_tuple] = record_names
863
 
            except ValueError:
864
 
                raise errors.SmartProtocolError(
865
 
                    'Repository data stream had invalid record name %r'
866
 
                    % (record_names,))
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'
 
1046
                        % (record_names,))
 
1047
                name_tuple = record_names[0]
 
1048
                yield name_tuple, record_bytes
868
1049
 
869
1050
    def insert_data_stream(self, stream):
870
1051
        self._ensure_real()
888
1069
        self._ensure_real()
889
1070
        return self._real_repository._check_for_inconsistent_revision_parents()
890
1071
 
 
1072
    def _make_parents_provider(self):
 
1073
        return self
 
1074
 
 
1075
    def _serialise_search_recipe(self, recipe):
 
1076
        """Serialise a graph search recipe.
 
1077
 
 
1078
        :param recipe: A search recipe (start, stop, count).
 
1079
        :return: Serialised bytes.
 
1080
        """
 
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))
 
1085
 
891
1086
 
892
1087
class RemoteBranchLockableFiles(LockableFiles):
893
1088
    """A 'LockableFiles' implementation that talks to a smart server.
1227
1422
        # format, because RemoteBranches can't be created at arbitrary URLs.
1228
1423
        # XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1229
1424
        # to_bzrdir.create_branch...
1230
 
        result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
 
1425
        self._ensure_real()
 
1426
        result = self._real_branch._format.initialize(to_bzrdir)
1231
1427
        self.copy_content_into(result, revision_id=revision_id)
1232
1428
        result.set_parent(self.bzrdir.root_transport.base)
1233
1429
        return result
1275
1471
        self._ensure_real()
1276
1472
        return self._real_branch.set_push_location(location)
1277
1473
 
1278
 
    def update_revisions(self, other, stop_revision=None):
 
1474
    def update_revisions(self, other, stop_revision=None, overwrite=False):
1279
1475
        self._ensure_real()
1280
1476
        return self._real_branch.update_revisions(
1281
 
            other, stop_revision=stop_revision)
 
1477
            other, stop_revision=stop_revision, overwrite=overwrite)
1282
1478
 
1283
1479
 
1284
1480
class RemoteBranchConfig(BranchConfig):