/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: John Arbash Meinel
  • Date: 2008-06-05 16:27:16 UTC
  • mfrom: (3475 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3476.
  • Revision ID: john@arbash-meinel.com-20080605162716-a3hn238tnctbfd8j
merge bzr.dev, resolve NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
28
28
    lockdir,
29
29
    repository,
30
30
    revision,
 
31
    symbol_versioning,
31
32
)
32
33
from bzrlib.branch import BranchReferenceFormat
33
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
34
35
from bzrlib.config import BranchConfig, TreeConfig
35
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
36
 
from bzrlib.errors import NoSuchRevision
 
37
from bzrlib.errors import (
 
38
    NoSuchRevision,
 
39
    SmartProtocolError,
 
40
    )
37
41
from bzrlib.lockable_files import LockableFiles
38
42
from bzrlib.pack import ContainerPushParser
39
43
from bzrlib.smart import client, vfs
40
44
from bzrlib.symbol_versioning import (
 
45
    deprecated_in,
41
46
    deprecated_method,
42
 
    zero_ninetyone,
43
47
    )
44
 
from bzrlib.revision import NULL_REVISION
 
48
from bzrlib.revision import ensure_null, NULL_REVISION
45
49
from bzrlib.trace import mutter, note, warning
46
50
 
 
51
 
47
52
# Note: RemoteBzrDirFormat is in bzrdir.py
48
53
 
49
54
class RemoteBzrDir(BzrDir):
61
66
        self._real_bzrdir = None
62
67
 
63
68
        if _client is None:
64
 
            self._shared_medium = transport.get_shared_medium()
65
 
            self._client = client._SmartClient(self._shared_medium)
 
69
            medium = transport.get_smart_medium()
 
70
            self._client = client._SmartClient(medium)
66
71
        else:
67
72
            self._client = _client
68
 
            self._shared_medium = None
69
73
            return
70
74
 
71
75
        path = self._path_for_remote_call(self._client)
118
122
    def get_branch_reference(self):
119
123
        """See BzrDir.get_branch_reference()."""
120
124
        path = self._path_for_remote_call(self._client)
121
 
        response = self._client.call('BzrDir.open_branch', path)
 
125
        try:
 
126
            response = self._client.call('BzrDir.open_branch', path)
 
127
        except errors.ErrorFromSmartServer, err:
 
128
            if err.error_tuple == ('nobranch',):
 
129
                raise errors.NotBranchError(path=self.root_transport.base)
 
130
            raise
122
131
        if response[0] == 'ok':
123
132
            if response[1] == '':
124
133
                # branch at this location.
126
135
            else:
127
136
                # a branch reference, use the existing BranchReference logic.
128
137
                return response[1]
129
 
        elif response == ('nobranch',):
130
 
            raise errors.NotBranchError(path=self.root_transport.base)
131
138
        else:
132
139
            raise errors.UnexpectedSmartServerResponse(response)
133
140
 
136
143
        return None, self.open_branch()
137
144
 
138
145
    def open_branch(self, _unsupported=False):
139
 
        assert _unsupported == False, 'unsupported flag support not implemented yet.'
 
146
        if _unsupported:
 
147
            raise NotImplementedError('unsupported flag support not implemented yet.')
140
148
        reference_url = self.get_branch_reference()
141
149
        if reference_url is None:
142
150
            # branch at this location.
149
157
    def open_repository(self):
150
158
        path = self._path_for_remote_call(self._client)
151
159
        verb = 'BzrDir.find_repositoryV2'
152
 
        response = self._client.call(verb, path)
153
 
        if (response == ('error', "Generic bzr smart protocol error: "
154
 
                "bad request '%s'" % verb) or
155
 
              response == ('error', "Generic bzr smart protocol error: "
156
 
                "bad request u'%s'" % verb)):
157
 
            verb = 'BzrDir.find_repository'
158
 
            response = self._client.call(verb, path)
159
 
        assert response[0] in ('ok', 'norepository'), \
160
 
            'unexpected response code %s' % (response,)
161
 
        if response[0] == 'norepository':
162
 
            raise errors.NoRepositoryPresent(self)
 
160
        try:
 
161
            try:
 
162
                response = self._client.call(verb, path)
 
163
            except errors.UnknownSmartMethod:
 
164
                verb = 'BzrDir.find_repository'
 
165
                response = self._client.call(verb, path)
 
166
        except errors.ErrorFromSmartServer, err:
 
167
            if err.error_verb == 'norepository':
 
168
                raise errors.NoRepositoryPresent(self)
 
169
            raise
 
170
        if response[0] != 'ok':
 
171
            raise errors.UnexpectedSmartServerResponse(response)
163
172
        if verb == 'BzrDir.find_repository':
164
173
            # servers that don't support the V2 method don't support external
165
174
            # references either.
166
175
            response = response + ('no', )
167
 
        assert len(response) == 5, 'incorrect response length %s' % (response,)
 
176
        if not (len(response) == 5):
 
177
            raise SmartProtocolError('incorrect response length %s' % (response,))
168
178
        if response[1] == '':
169
179
            format = RemoteRepositoryFormat()
170
180
            format.rich_root_data = (response[2] == 'yes')
228
238
    _matchingbzrdir = RemoteBzrDirFormat
229
239
 
230
240
    def initialize(self, a_bzrdir, shared=False):
231
 
        assert isinstance(a_bzrdir, RemoteBzrDir), \
232
 
            '%r is not a RemoteBzrDir' % (a_bzrdir,)
 
241
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
242
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
233
243
        return a_bzrdir.create_repository(shared=shared)
234
244
    
235
245
    def open(self, a_bzrdir):
236
 
        assert isinstance(a_bzrdir, RemoteBzrDir)
 
246
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
247
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
237
248
        return a_bzrdir.open_repository()
238
249
 
239
250
    def get_format_description(self):
276
287
            self._real_repository = None
277
288
        self.bzrdir = remote_bzrdir
278
289
        if _client is None:
279
 
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
290
            self._client = remote_bzrdir._client
280
291
        else:
281
292
            self._client = _client
282
293
        self._format = format
360
371
        self._ensure_real()
361
372
        return self._real_repository._generate_text_key_index()
362
373
 
 
374
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
363
375
    def get_revision_graph(self, revision_id=None):
364
376
        """See Repository.get_revision_graph()."""
 
377
        return self._get_revision_graph(revision_id)
 
378
 
 
379
    def _get_revision_graph(self, revision_id):
 
380
        """Private method for using with old (< 1.2) servers to fallback."""
365
381
        if revision_id is None:
366
382
            revision_id = ''
367
383
        elif revision.is_null(revision_id):
368
384
            return {}
369
385
 
370
386
        path = self.bzrdir._path_for_remote_call(self._client)
371
 
        assert type(revision_id) is str
372
 
        response = self._client.call_expecting_body(
373
 
            'Repository.get_revision_graph', path, revision_id)
374
 
        if response[0][0] not in ['ok', 'nosuchrevision']:
375
 
            raise errors.UnexpectedSmartServerResponse(response[0])
376
 
        if response[0][0] == 'ok':
377
 
            coded = response[1].read_body_bytes()
378
 
            if coded == '':
379
 
                # no revisions in this repository!
380
 
                return {}
381
 
            lines = coded.split('\n')
382
 
            revision_graph = {}
383
 
            for line in lines:
384
 
                d = tuple(line.split())
385
 
                revision_graph[d[0]] = d[1:]
386
 
                
387
 
            return revision_graph
388
 
        else:
389
 
            response_body = response[1].read_body_bytes()
390
 
            assert response_body == ''
391
 
            raise NoSuchRevision(self, revision_id)
 
387
        try:
 
388
            response = self._client.call_expecting_body(
 
389
                'Repository.get_revision_graph', path, revision_id)
 
390
        except errors.ErrorFromSmartServer, err:
 
391
            if err.error_verb == 'nosuchrevision':
 
392
                raise NoSuchRevision(self, revision_id)
 
393
            raise
 
394
        response_tuple, response_handler = response
 
395
        if response_tuple[0] != 'ok':
 
396
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
397
        coded = response_handler.read_body_bytes()
 
398
        if coded == '':
 
399
            # no revisions in this repository!
 
400
            return {}
 
401
        lines = coded.split('\n')
 
402
        revision_graph = {}
 
403
        for line in lines:
 
404
            d = tuple(line.split())
 
405
            revision_graph[d[0]] = d[1:]
 
406
            
 
407
        return revision_graph
392
408
 
393
409
    def has_revision(self, revision_id):
394
410
        """See Repository.has_revision()."""
396
412
            # The null revision is always present.
397
413
            return True
398
414
        path = self.bzrdir._path_for_remote_call(self._client)
399
 
        response = self._client.call('Repository.has_revision', path, revision_id)
400
 
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
415
        response = self._client.call(
 
416
            'Repository.has_revision', path, revision_id)
 
417
        if response[0] not in ('yes', 'no'):
 
418
            raise errors.UnexpectedSmartServerResponse(response)
401
419
        return response[0] == 'yes'
402
420
 
403
421
    def has_revisions(self, revision_ids):
434
452
            fmt_committers = 'no'
435
453
        else:
436
454
            fmt_committers = 'yes'
437
 
        response = self._client.call_expecting_body(
 
455
        response_tuple, response_handler = self._client.call_expecting_body(
438
456
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
439
 
        assert response[0][0] == 'ok', \
440
 
            'unexpected response code %s' % (response[0],)
 
457
        if response_tuple[0] != 'ok':
 
458
            raise errors.UnexpectedSmartServerResponse(response_tuple)
441
459
 
442
 
        body = response[1].read_body_bytes()
 
460
        body = response_handler.read_body_bytes()
443
461
        result = {}
444
462
        for line in body.split('\n'):
445
463
            if not line:
480
498
        """See Repository.is_shared()."""
481
499
        path = self.bzrdir._path_for_remote_call(self._client)
482
500
        response = self._client.call('Repository.is_shared', path)
483
 
        assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
 
501
        if response[0] not in ('yes', 'no'):
 
502
            raise SmartProtocolError('unexpected response code %s' % (response,))
484
503
        return response[0] == 'yes'
485
504
 
486
505
    def is_write_locked(self):
503
522
        path = self.bzrdir._path_for_remote_call(self._client)
504
523
        if token is None:
505
524
            token = ''
506
 
        response = self._client.call('Repository.lock_write', path, token)
 
525
        try:
 
526
            response = self._client.call('Repository.lock_write', path, token)
 
527
        except errors.ErrorFromSmartServer, err:
 
528
            if err.error_verb == 'LockContention':
 
529
                raise errors.LockContention('(remote lock)')
 
530
            elif err.error_verb == 'UnlockableTransport':
 
531
                raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
532
            elif err.error_verb == 'LockFailed':
 
533
                raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
534
            raise
 
535
 
507
536
        if response[0] == 'ok':
508
537
            ok, token = response
509
538
            return token
510
 
        elif response[0] == 'LockContention':
511
 
            raise errors.LockContention('(remote lock)')
512
 
        elif response[0] == 'UnlockableTransport':
513
 
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
514
 
        elif response[0] == 'LockFailed':
515
 
            raise errors.LockFailed(response[1], response[2])
516
539
        else:
517
540
            raise errors.UnexpectedSmartServerResponse(response)
518
541
 
556
579
        :param repository: The repository to fallback to for non-hpss
557
580
            implemented operations.
558
581
        """
559
 
        assert not isinstance(repository, RemoteRepository)
 
582
        if isinstance(repository, RemoteRepository):
 
583
            raise AssertionError()
560
584
        self._real_repository = repository
561
585
        if self._lock_mode == 'w':
562
586
            # if we are already locked, the real repository must be able to
581
605
        if not token:
582
606
            # with no token the remote repository is not persistently locked.
583
607
            return
584
 
        response = self._client.call('Repository.unlock', path, token)
 
608
        try:
 
609
            response = self._client.call('Repository.unlock', path, token)
 
610
        except errors.ErrorFromSmartServer, err:
 
611
            if err.error_verb == 'TokenMismatch':
 
612
                raise errors.TokenMismatch(token, '(remote token)')
 
613
            raise
585
614
        if response == ('ok',):
586
615
            return
587
 
        elif response[0] == 'TokenMismatch':
588
 
            raise errors.TokenMismatch(token, '(remote token)')
589
616
        else:
590
617
            raise errors.UnexpectedSmartServerResponse(response)
591
618
 
629
656
        """
630
657
        import tempfile
631
658
        path = self.bzrdir._path_for_remote_call(self._client)
632
 
        response, protocol = self._client.call_expecting_body(
633
 
            'Repository.tarball', path, compression)
 
659
        try:
 
660
            response, protocol = self._client.call_expecting_body(
 
661
                'Repository.tarball', path, compression)
 
662
        except errors.UnknownSmartMethod:
 
663
            protocol.cancel_read_body()
 
664
            return None
634
665
        if response[0] == 'ok':
635
666
            # Extract the tarball and return it
636
667
            t = tempfile.NamedTemporaryFile()
638
669
            t.write(protocol.read_body_bytes())
639
670
            t.seek(0)
640
671
            return t
641
 
        if (response == ('error', "Generic bzr smart protocol error: "
642
 
                "bad request 'Repository.tarball'") or
643
 
              response == ('error', "Generic bzr smart protocol error: "
644
 
                "bad request u'Repository.tarball'")):
645
 
            protocol.cancel_read_body()
646
 
            return None
647
672
        raise errors.UnexpectedSmartServerResponse(response)
648
673
 
649
674
    def sprout(self, to_bzrdir, revision_id=None):
713
738
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
714
739
 
715
740
    def make_working_trees(self):
716
 
        """RemoteRepositories never create working trees by default."""
717
 
        return False
 
741
        """See Repository.make_working_trees"""
 
742
        self._ensure_real()
 
743
        return self._real_repository.make_working_trees()
718
744
 
719
745
    def revision_ids_to_search_result(self, result_set):
720
746
        """Convert a set of revision ids to a graph SearchResult."""
805
831
            ancestry.update(parent_map)
806
832
        present_keys = [k for k in keys if k in ancestry]
807
833
        if 'hpss' in debug.debug_flags:
808
 
            self._requested_parents.update(present_keys)
809
 
            mutter('Current RemoteRepository graph hit rate: %d%%',
810
 
                100.0 * len(self._requested_parents) / len(ancestry))
 
834
            if self._requested_parents is not None and len(ancestry) != 0:
 
835
                self._requested_parents.update(present_keys)
 
836
                mutter('Current RemoteRepository graph hit rate: %d%%',
 
837
                    100.0 * len(self._requested_parents) / len(ancestry))
811
838
        return dict((k, ancestry[k]) for k in present_keys)
812
839
 
813
 
    def _response_is_unknown_method(self, response, verb):
814
 
        """Return True if response is an unknonwn method response to verb.
815
 
        
816
 
        :param response: The response from a smart client call_expecting_body
817
 
            call.
818
 
        :param verb: The verb used in that call.
819
 
        :return: True if an unknown method was encountered.
820
 
        """
821
 
        # This might live better on
822
 
        # bzrlib.smart.protocol.SmartClientRequestProtocolOne
823
 
        if (response[0] == ('error', "Generic bzr smart protocol error: "
824
 
                "bad request '%s'" % verb) or
825
 
              response[0] == ('error', "Generic bzr smart protocol error: "
826
 
                "bad request u'%s'" % verb)):
827
 
           response[1].cancel_read_body()
828
 
           return True
829
 
        return False
830
 
 
831
840
    def _get_parent_map(self, keys):
832
841
        """Helper for get_parent_map that performs the RPC."""
833
 
        medium = self._client.get_smart_medium()
 
842
        medium = self._client._medium
834
843
        if not medium._remote_is_at_least_1_2:
835
844
            # We already found out that the server can't understand
836
845
            # Repository.get_parent_map requests, so just fetch the whole
837
846
            # graph.
838
 
            return self.get_revision_graph()
 
847
            # XXX: Note that this will issue a deprecation warning. This is ok
 
848
            # :- its because we're working with a deprecated server anyway, and
 
849
            # the user will almost certainly have seen a warning about the
 
850
            # server version already.
 
851
            rg = self.get_revision_graph()
 
852
            # There is an api discrepency between get_parent_map and
 
853
            # get_revision_graph. Specifically, a "key:()" pair in
 
854
            # get_revision_graph just means a node has no parents. For
 
855
            # "get_parent_map" it means the node is a ghost. So fix up the
 
856
            # graph to correct this.
 
857
            #   https://bugs.launchpad.net/bzr/+bug/214894
 
858
            # There is one other "bug" which is that ghosts in
 
859
            # get_revision_graph() are not returned at all. But we won't worry
 
860
            # about that for now.
 
861
            for node_id, parent_ids in rg.iteritems():
 
862
                if parent_ids == ():
 
863
                    rg[node_id] = (NULL_REVISION,)
 
864
            rg[NULL_REVISION] = ()
 
865
            return rg
839
866
 
840
867
        keys = set(keys)
 
868
        if None in keys:
 
869
            raise ValueError('get_parent_map(None) is not valid')
841
870
        if NULL_REVISION in keys:
842
871
            keys.discard(NULL_REVISION)
843
872
            found_parents = {NULL_REVISION:()}
871
900
        body = self._serialise_search_recipe(recipe)
872
901
        path = self.bzrdir._path_for_remote_call(self._client)
873
902
        for key in keys:
874
 
            assert type(key) is str
 
903
            if type(key) is not str:
 
904
                raise ValueError(
 
905
                    "key %r not a plain string" % (key,))
875
906
        verb = 'Repository.get_parent_map'
876
907
        args = (path,) + tuple(keys)
877
 
        response = self._client.call_with_body_bytes_expecting_body(
878
 
            verb, args, self._serialise_search_recipe(recipe))
879
 
        if self._response_is_unknown_method(response, verb):
 
908
        try:
 
909
            response = self._client.call_with_body_bytes_expecting_body(
 
910
                verb, args, self._serialise_search_recipe(recipe))
 
911
        except errors.UnknownSmartMethod:
880
912
            # Server does not support this method, so get the whole graph.
881
913
            # Worse, we have to force a disconnection, because the server now
882
914
            # doesn't realise it has a body on the wire to consume, so the
888
920
            # To avoid having to disconnect repeatedly, we keep track of the
889
921
            # fact the server doesn't understand remote methods added in 1.2.
890
922
            medium._remote_is_at_least_1_2 = False
891
 
            return self.get_revision_graph()
892
 
        elif response[0][0] not in ['ok']:
893
 
            reponse[1].cancel_read_body()
894
 
            raise errors.UnexpectedSmartServerResponse(response[0])
895
 
        if response[0][0] == 'ok':
896
 
            coded = bz2.decompress(response[1].read_body_bytes())
 
923
            return self.get_revision_graph(None)
 
924
        response_tuple, response_handler = response
 
925
        if response_tuple[0] not in ['ok']:
 
926
            response_handler.cancel_read_body()
 
927
            raise errors.UnexpectedSmartServerResponse(response_tuple)
 
928
        if response_tuple[0] == 'ok':
 
929
            coded = bz2.decompress(response_handler.read_body_bytes())
897
930
            if coded == '':
898
931
                # no revisions found
899
932
                return {}
914
947
        return self._real_repository.get_signature_text(revision_id)
915
948
 
916
949
    @needs_read_lock
 
950
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
917
951
    def get_revision_graph_with_ghosts(self, revision_ids=None):
918
952
        self._ensure_real()
919
953
        return self._real_repository.get_revision_graph_with_ghosts(
1005
1039
        return self._real_repository.pack()
1006
1040
 
1007
1041
    def set_make_working_trees(self, new_value):
1008
 
        raise NotImplementedError(self.set_make_working_trees)
 
1042
        self._ensure_real()
 
1043
        self._real_repository.set_make_working_trees(new_value)
1009
1044
 
1010
1045
    @needs_write_lock
1011
1046
    def sign_revision(self, revision_id, gpg_strategy):
1044
1079
        return self._real_repository.has_signature_for_revision_id(revision_id)
1045
1080
 
1046
1081
    def get_data_stream_for_search(self, search):
1047
 
        medium = self._client.get_smart_medium()
 
1082
        medium = self._client._medium
1048
1083
        if not medium._remote_is_at_least_1_2:
1049
1084
            self._ensure_real()
1050
1085
            return self._real_repository.get_data_stream_for_search(search)
1051
1086
        REQUEST_NAME = 'Repository.stream_revisions_chunked'
1052
1087
        path = self.bzrdir._path_for_remote_call(self._client)
1053
1088
        body = self._serialise_search_recipe(search.get_recipe())
1054
 
        response, protocol = self._client.call_with_body_bytes_expecting_body(
1055
 
            REQUEST_NAME, (path,), body)
1056
 
 
1057
 
        if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
 
1089
        try:
 
1090
            result = self._client.call_with_body_bytes_expecting_body(
 
1091
                REQUEST_NAME, (path,), body)
 
1092
            response, protocol = result
 
1093
        except errors.UnknownSmartMethod:
1058
1094
            # Server does not support this method, so fall back to VFS.
1059
1095
            # Worse, we have to force a disconnection, because the server now
1060
1096
            # doesn't realise it has a body on the wire to consume, so the
1150
1186
        self._dir_mode = None
1151
1187
        self._file_mode = None
1152
1188
 
1153
 
    def get(self, path):
1154
 
        """'get' a remote path as per the LockableFiles interface.
1155
 
 
1156
 
        :param path: the file to 'get'. If this is 'branch.conf', we do not
1157
 
             just retrieve a file, instead we ask the smart server to generate
1158
 
             a configuration for us - which is retrieved as an INI file.
1159
 
        """
1160
 
        if path == 'branch.conf':
1161
 
            path = self.bzrdir._path_for_remote_call(self._client)
1162
 
            response = self._client.call_expecting_body(
1163
 
                'Branch.get_config_file', path)
1164
 
            assert response[0][0] == 'ok', \
1165
 
                'unexpected response code %s' % (response[0],)
1166
 
            return StringIO(response[1].read_body_bytes())
1167
 
        else:
1168
 
            # VFS fallback.
1169
 
            return LockableFiles.get(self, path)
1170
 
 
1171
1189
 
1172
1190
class RemoteBranchFormat(branch.BranchFormat):
1173
1191
 
1182
1200
        return 'Remote BZR Branch'
1183
1201
 
1184
1202
    def open(self, a_bzrdir):
1185
 
        assert isinstance(a_bzrdir, RemoteBzrDir)
1186
1203
        return a_bzrdir.open_branch()
1187
1204
 
1188
1205
    def initialize(self, a_bzrdir):
1189
 
        assert isinstance(a_bzrdir, RemoteBzrDir)
1190
1206
        return a_bzrdir.create_branch()
1191
1207
 
1192
1208
    def supports_tags(self):
1218
1234
        if _client is not None:
1219
1235
            self._client = _client
1220
1236
        else:
1221
 
            self._client = client._SmartClient(self.bzrdir._shared_medium)
 
1237
            self._client = remote_bzrdir._client
1222
1238
        self.repository = remote_repository
1223
1239
        if real_branch is not None:
1224
1240
            self._real_branch = real_branch
1238
1254
        self._control_files = None
1239
1255
        self._lock_mode = None
1240
1256
        self._lock_token = None
 
1257
        self._repo_lock_token = None
1241
1258
        self._lock_count = 0
1242
1259
        self._leave_lock = False
1243
1260
 
 
1261
    def _get_real_transport(self):
 
1262
        # if we try vfs access, return the real branch's vfs transport
 
1263
        self._ensure_real()
 
1264
        return self._real_branch._transport
 
1265
 
 
1266
    _transport = property(_get_real_transport)
 
1267
 
1244
1268
    def __str__(self):
1245
1269
        return "%s(%s)" % (self.__class__.__name__, self.base)
1246
1270
 
1251
1275
 
1252
1276
        Used before calls to self._real_branch.
1253
1277
        """
1254
 
        if not self._real_branch:
1255
 
            assert vfs.vfs_enabled()
 
1278
        if self._real_branch is None:
 
1279
            if not vfs.vfs_enabled():
 
1280
                raise AssertionError('smart server vfs must be enabled '
 
1281
                    'to use vfs implementation')
1256
1282
            self.bzrdir._ensure_real()
1257
1283
            self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1258
1284
            # Give the remote repository the matching real repo.
1303
1329
            repo_token = self.repository.lock_write()
1304
1330
            self.repository.unlock()
1305
1331
        path = self.bzrdir._path_for_remote_call(self._client)
1306
 
        response = self._client.call('Branch.lock_write', path, branch_token,
1307
 
                                     repo_token or '')
1308
 
        if response[0] == 'ok':
1309
 
            ok, branch_token, repo_token = response
1310
 
            return branch_token, repo_token
1311
 
        elif response[0] == 'LockContention':
1312
 
            raise errors.LockContention('(remote lock)')
1313
 
        elif response[0] == 'TokenMismatch':
1314
 
            raise errors.TokenMismatch(token, '(remote token)')
1315
 
        elif response[0] == 'UnlockableTransport':
1316
 
            raise errors.UnlockableTransport(self.bzrdir.root_transport)
1317
 
        elif response[0] == 'ReadOnlyError':
1318
 
            raise errors.ReadOnlyError(self)
1319
 
        elif response[0] == 'LockFailed':
1320
 
            raise errors.LockFailed(response[1], response[2])
1321
 
        else:
 
1332
        try:
 
1333
            response = self._client.call(
 
1334
                'Branch.lock_write', path, branch_token, repo_token or '')
 
1335
        except errors.ErrorFromSmartServer, err:
 
1336
            if err.error_verb == 'LockContention':
 
1337
                raise errors.LockContention('(remote lock)')
 
1338
            elif err.error_verb == 'TokenMismatch':
 
1339
                raise errors.TokenMismatch(token, '(remote token)')
 
1340
            elif err.error_verb == 'UnlockableTransport':
 
1341
                raise errors.UnlockableTransport(self.bzrdir.root_transport)
 
1342
            elif err.error_verb == 'ReadOnlyError':
 
1343
                raise errors.ReadOnlyError(self)
 
1344
            elif err.error_verb == 'LockFailed':
 
1345
                raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
1346
            raise
 
1347
        if response[0] != 'ok':
1322
1348
            raise errors.UnexpectedSmartServerResponse(response)
 
1349
        ok, branch_token, repo_token = response
 
1350
        return branch_token, repo_token
1323
1351
            
1324
1352
    def lock_write(self, token=None):
1325
1353
        if not self._lock_mode:
1326
1354
            remote_tokens = self._remote_lock_write(token)
1327
1355
            self._lock_token, self._repo_lock_token = remote_tokens
1328
 
            assert self._lock_token, 'Remote server did not return a token!'
 
1356
            if not self._lock_token:
 
1357
                raise SmartProtocolError('Remote server did not return a token!')
1329
1358
            # TODO: We really, really, really don't want to call _ensure_real
1330
1359
            # here, but it's the easiest way to ensure coherency between the
1331
1360
            # state of the RemoteBranch and RemoteRepository objects and the
1361
1390
 
1362
1391
    def _unlock(self, branch_token, repo_token):
1363
1392
        path = self.bzrdir._path_for_remote_call(self._client)
1364
 
        response = self._client.call('Branch.unlock', path, branch_token,
1365
 
                                     repo_token or '')
 
1393
        try:
 
1394
            response = self._client.call('Branch.unlock', path, branch_token,
 
1395
                                         repo_token or '')
 
1396
        except errors.ErrorFromSmartServer, err:
 
1397
            if err.error_verb == 'TokenMismatch':
 
1398
                raise errors.TokenMismatch(
 
1399
                    str((branch_token, repo_token)), '(remote tokens)')
 
1400
            raise
1366
1401
        if response == ('ok',):
1367
1402
            return
1368
 
        elif response[0] == 'TokenMismatch':
1369
 
            raise errors.TokenMismatch(
1370
 
                str((branch_token, repo_token)), '(remote tokens)')
1371
 
        else:
1372
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1403
        raise errors.UnexpectedSmartServerResponse(response)
1373
1404
 
1374
1405
    def unlock(self):
1375
1406
        self._lock_count -= 1
1390
1421
                # Only write-locked branched need to make a remote method call
1391
1422
                # to perfom the unlock.
1392
1423
                return
1393
 
            assert self._lock_token, 'Locked, but no token!'
 
1424
            if not self._lock_token:
 
1425
                raise AssertionError('Locked, but no token!')
1394
1426
            branch_token = self._lock_token
1395
1427
            repo_token = self._repo_lock_token
1396
1428
            self._lock_token = None
1416
1448
        """See Branch.last_revision_info()."""
1417
1449
        path = self.bzrdir._path_for_remote_call(self._client)
1418
1450
        response = self._client.call('Branch.last_revision_info', path)
1419
 
        assert response[0] == 'ok', 'unexpected response code %s' % (response,)
 
1451
        if response[0] != 'ok':
 
1452
            raise SmartProtocolError('unexpected response code %s' % (response,))
1420
1453
        revno = int(response[1])
1421
1454
        last_revision = response[2]
1422
1455
        return (revno, last_revision)
1424
1457
    def _gen_revision_history(self):
1425
1458
        """See Branch._gen_revision_history()."""
1426
1459
        path = self.bzrdir._path_for_remote_call(self._client)
1427
 
        response = self._client.call_expecting_body(
 
1460
        response_tuple, response_handler = self._client.call_expecting_body(
1428
1461
            'Branch.revision_history', path)
1429
 
        assert response[0][0] == 'ok', ('unexpected response code %s'
1430
 
                                        % (response[0],))
1431
 
        result = response[1].read_body_bytes().split('\x00')
 
1462
        if response_tuple[0] != 'ok':
 
1463
            raise UnexpectedSmartServerResponse(response_tuple)
 
1464
        result = response_handler.read_body_bytes().split('\x00')
1432
1465
        if result == ['']:
1433
1466
            return []
1434
1467
        return result
1444
1477
        else:
1445
1478
            rev_id = rev_history[-1]
1446
1479
        self._clear_cached_state()
1447
 
        response = self._client.call('Branch.set_last_revision',
1448
 
            path, self._lock_token, self._repo_lock_token, rev_id)
1449
 
        if response[0] == 'NoSuchRevision':
1450
 
            raise NoSuchRevision(self, rev_id)
1451
 
        else:
1452
 
            assert response == ('ok',), (
1453
 
                'unexpected response code %r' % (response,))
 
1480
        try:
 
1481
            response = self._client.call('Branch.set_last_revision',
 
1482
                path, self._lock_token, self._repo_lock_token, rev_id)
 
1483
        except errors.ErrorFromSmartServer, err:
 
1484
            if err.error_verb == 'NoSuchRevision':
 
1485
                raise NoSuchRevision(self, rev_id)
 
1486
            raise
 
1487
        if response != ('ok',):
 
1488
            raise errors.UnexpectedSmartServerResponse(response)
1454
1489
        self._cache_revision_history(rev_history)
1455
1490
 
1456
1491
    def get_parent(self):
1461
1496
        self._ensure_real()
1462
1497
        return self._real_branch.set_parent(url)
1463
1498
        
1464
 
    def get_config(self):
1465
 
        return RemoteBranchConfig(self)
1466
 
 
1467
1499
    def sprout(self, to_bzrdir, revision_id=None):
1468
1500
        # Like Branch.sprout, except that it sprouts a branch in the default
1469
1501
        # format, because RemoteBranches can't be created at arbitrary URLs.
1498
1530
    def is_locked(self):
1499
1531
        return self._lock_count >= 1
1500
1532
 
 
1533
    @needs_write_lock
1501
1534
    def set_last_revision_info(self, revno, revision_id):
1502
 
        self._ensure_real()
1503
 
        self._clear_cached_state()
1504
 
        return self._real_branch.set_last_revision_info(revno, revision_id)
 
1535
        revision_id = ensure_null(revision_id)
 
1536
        path = self.bzrdir._path_for_remote_call(self._client)
 
1537
        try:
 
1538
            response = self._client.call('Branch.set_last_revision_info',
 
1539
                path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
 
1540
        except errors.UnknownSmartMethod:
 
1541
            self._ensure_real()
 
1542
            self._clear_cached_state()
 
1543
            return self._real_branch.set_last_revision_info(revno, revision_id)
 
1544
        except errors.ErrorFromSmartServer, err:
 
1545
            if err.error_verb == 'NoSuchRevision':
 
1546
                raise NoSuchRevision(self, err.error_args[0])
 
1547
            raise
 
1548
        if response == ('ok',):
 
1549
            self._clear_cached_state()
 
1550
        else:
 
1551
            raise errors.UnexpectedSmartServerResponse(response)
1505
1552
 
1506
1553
    def generate_revision_history(self, revision_id, last_rev=None,
1507
1554
                                  other_branch=None):
1518
1565
        self._ensure_real()
1519
1566
        return self._real_branch.set_push_location(location)
1520
1567
 
1521
 
    def update_revisions(self, other, stop_revision=None, overwrite=False):
 
1568
    def update_revisions(self, other, stop_revision=None, overwrite=False,
 
1569
                         graph=None):
1522
1570
        self._ensure_real()
1523
1571
        return self._real_branch.update_revisions(
1524
 
            other, stop_revision=stop_revision, overwrite=overwrite)
1525
 
 
1526
 
 
1527
 
class RemoteBranchConfig(BranchConfig):
1528
 
 
1529
 
    def username(self):
1530
 
        self.branch._ensure_real()
1531
 
        return self.branch._real_branch.get_config().username()
1532
 
 
1533
 
    def _get_branch_data_config(self):
1534
 
        self.branch._ensure_real()
1535
 
        if self._branch_data_config is None:
1536
 
            self._branch_data_config = TreeConfig(self.branch._real_branch)
1537
 
        return self._branch_data_config
 
1572
            other, stop_revision=stop_revision, overwrite=overwrite,
 
1573
            graph=graph)
1538
1574
 
1539
1575
 
1540
1576
def _extract_tar(tar, to_dir):