~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-06 06:48:25 UTC
  • mfrom: (4070.8.6 debug-config)
  • Revision ID: pqm@pqm.ubuntu.com-20090306064825-kbpwggw21dygeix6
(mbp) debug_flags configuration option

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
# TODO: At some point, handle upgrades by just passing the whole request
18
18
# across to run on the server.
22
22
from bzrlib import (
23
23
    branch,
24
24
    bzrdir,
25
 
    config,
26
25
    debug,
27
26
    errors,
28
27
    graph,
74
73
def response_tuple_to_repo_format(response):
75
74
    """Convert a response tuple describing a repository format to a format."""
76
75
    format = RemoteRepositoryFormat()
77
 
    format._rich_root_data = (response[0] == 'yes')
78
 
    format._supports_tree_reference = (response[1] == 'yes')
79
 
    format._supports_external_lookups = (response[2] == 'yes')
 
76
    format.rich_root_data = (response[0] == 'yes')
 
77
    format.supports_tree_reference = (response[1] == 'yes')
 
78
    format.supports_external_lookups = (response[2] == 'yes')
80
79
    format._network_name = response[3]
81
80
    return format
82
81
 
152
151
        try:
153
152
            response = self._call(verb, path, stacking)
154
153
        except errors.UnknownSmartMethod:
155
 
            medium._remember_remote_is_before((1, 13))
156
154
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
157
 
        except errors.UnknownErrorFromSmartServer, err:
158
 
            if err.error_tuple != ('BranchReference',):
159
 
                raise
160
 
            # We need to resolve the branch reference to determine the
161
 
            # cloning_metadir.  This causes unnecessary RPCs to open the
162
 
            # referenced branch (and bzrdir, etc) but only when the caller
163
 
            # didn't already resolve the branch reference.
164
 
            referenced_branch = self.open_branch()
165
 
            return referenced_branch.bzrdir.cloning_metadir()
166
155
        if len(response) != 3:
167
156
            raise errors.UnexpectedSmartServerResponse(response)
168
157
        control_name, repo_name, branch_info = response
173
162
        if repo_name:
174
163
            format.repository_format = repository.network_format_registry.get(
175
164
                repo_name)
176
 
        if branch_ref == 'ref':
 
165
        if branch_ref == 'reference':
177
166
            # XXX: we need possible_transports here to avoid reopening the
178
167
            # connection to the referenced location
179
168
            ref_bzrdir = BzrDir.open(branch_name)
180
169
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
181
170
            format.set_branch_format(branch_format)
182
 
        elif branch_ref == 'branch':
 
171
        elif branch_ref == 'direct':
183
172
            if branch_name:
184
173
                format.set_branch_format(
185
174
                    branch.network_format_registry.get(branch_name))
237
226
 
238
227
    def get_branch_reference(self):
239
228
        """See BzrDir.get_branch_reference()."""
240
 
        response = self._get_branch_reference()
241
 
        if response[0] == 'ref':
242
 
            return response[1]
243
 
        else:
244
 
            return None
245
 
 
246
 
    def _get_branch_reference(self):
247
229
        path = self._path_for_remote_call(self._client)
248
 
        medium = self._client._medium
249
 
        if not medium._is_remote_before((1, 13)):
250
 
            try:
251
 
                response = self._call('BzrDir.open_branchV2', path)
252
 
                if response[0] not in ('ref', 'branch'):
253
 
                    raise errors.UnexpectedSmartServerResponse(response)
254
 
                return response
255
 
            except errors.UnknownSmartMethod:
256
 
                medium._remember_remote_is_before((1, 13))
257
230
        response = self._call('BzrDir.open_branch', path)
258
 
        if response[0] != 'ok':
 
231
        if response[0] == 'ok':
 
232
            if response[1] == '':
 
233
                # branch at this location.
 
234
                return None
 
235
            else:
 
236
                # a branch reference, use the existing BranchReference logic.
 
237
                return response[1]
 
238
        else:
259
239
            raise errors.UnexpectedSmartServerResponse(response)
260
 
        if response[1] != '':
261
 
            return ('ref', response[1])
262
 
        else:
263
 
            return ('branch', '')
264
240
 
265
241
    def _get_tree_branch(self):
266
242
        """See BzrDir._get_tree_branch()."""
267
243
        return None, self.open_branch()
268
244
 
269
 
    def open_branch(self, _unsupported=False, ignore_fallbacks=False):
 
245
    def open_branch(self, _unsupported=False):
270
246
        if _unsupported:
271
247
            raise NotImplementedError('unsupported flag support not implemented yet.')
272
248
        if self._next_open_branch_result is not None:
274
250
            result = self._next_open_branch_result
275
251
            self._next_open_branch_result = None
276
252
            return result
277
 
        response = self._get_branch_reference()
278
 
        if response[0] == 'ref':
 
253
        reference_url = self.get_branch_reference()
 
254
        if reference_url is None:
 
255
            # branch at this location.
 
256
            return RemoteBranch(self, self.find_repository())
 
257
        else:
279
258
            # a branch reference, use the existing BranchReference logic.
280
259
            format = BranchReferenceFormat()
281
 
            return format.open(self, _found=True, location=response[1],
282
 
                ignore_fallbacks=ignore_fallbacks)
283
 
        branch_format_name = response[1]
284
 
        if not branch_format_name:
285
 
            branch_format_name = None
286
 
        format = RemoteBranchFormat(network_name=branch_format_name)
287
 
        return RemoteBranch(self, self.find_repository(), format=format,
288
 
            setup_stacking=not ignore_fallbacks)
 
260
            return format.open(self, _found=True, location=reference_url)
289
261
 
290
262
    def _open_repo_v1(self, path):
291
263
        verb = 'BzrDir.find_repository'
314
286
        medium = self._client._medium
315
287
        if medium._is_remote_before((1, 13)):
316
288
            raise errors.UnknownSmartMethod(verb)
317
 
        try:
318
 
            response = self._call(verb, path)
319
 
        except errors.UnknownSmartMethod:
320
 
            medium._remember_remote_is_before((1, 13))
321
 
            raise
 
289
        response = self._call(verb, path)
322
290
        if response[0] != 'ok':
323
291
            raise errors.UnexpectedSmartServerResponse(response)
324
292
        return response, None
424
392
        self._custom_format = None
425
393
        self._network_name = None
426
394
        self._creating_bzrdir = None
427
 
        self._supports_external_lookups = None
428
 
        self._supports_tree_reference = None
429
 
        self._rich_root_data = None
430
 
 
431
 
    @property
432
 
    def fast_deltas(self):
433
 
        self._ensure_real()
434
 
        return self._custom_format.fast_deltas
435
 
 
436
 
    @property
437
 
    def rich_root_data(self):
438
 
        if self._rich_root_data is None:
439
 
            self._ensure_real()
440
 
            self._rich_root_data = self._custom_format.rich_root_data
441
 
        return self._rich_root_data
442
 
 
443
 
    @property
444
 
    def supports_external_lookups(self):
445
 
        if self._supports_external_lookups is None:
446
 
            self._ensure_real()
447
 
            self._supports_external_lookups = \
448
 
                self._custom_format.supports_external_lookups
449
 
        return self._supports_external_lookups
450
 
 
451
 
    @property
452
 
    def supports_tree_reference(self):
453
 
        if self._supports_tree_reference is None:
454
 
            self._ensure_real()
455
 
            self._supports_tree_reference = \
456
 
                self._custom_format.supports_tree_reference
457
 
        return self._supports_tree_reference
458
395
 
459
396
    def _vfs_initialize(self, a_bzrdir, shared):
460
397
        """Helper for common code in initialize."""
508
445
            response = a_bzrdir._call(verb, path, network_name, shared_str)
509
446
        except errors.UnknownSmartMethod:
510
447
            # Fallback - use vfs methods
511
 
            medium._remember_remote_is_before((1, 13))
512
448
            return self._vfs_initialize(a_bzrdir, shared)
513
449
        else:
514
450
            # Turn the response into a RemoteRepository object.
548
484
        return 'bzr remote repository'
549
485
 
550
486
    def __eq__(self, other):
551
 
        return self.__class__ is other.__class__
 
487
        return self.__class__ == other.__class__
552
488
 
553
489
    def check_conversion_target(self, target_format):
554
490
        if self.rich_root_data and not target_format.rich_root_data:
626
562
    def abort_write_group(self, suppress_errors=False):
627
563
        """Complete a write group on the decorated repository.
628
564
 
629
 
        Smart methods perform operations in a single step so this API
 
565
        Smart methods peform operations in a single step so this api
630
566
        is not really applicable except as a compatibility thunk
631
567
        for older plugins that don't use e.g. the CommitBuilder
632
568
        facility.
637
573
        return self._real_repository.abort_write_group(
638
574
            suppress_errors=suppress_errors)
639
575
 
640
 
    @property
641
 
    def chk_bytes(self):
642
 
        """Decorate the real repository for now.
643
 
 
644
 
        In the long term a full blown network facility is needed to avoid
645
 
        creating a real repository object locally.
646
 
        """
647
 
        self._ensure_real()
648
 
        return self._real_repository.chk_bytes
649
 
 
650
576
    def commit_write_group(self):
651
577
        """Complete a write group on the decorated repository.
652
578
 
653
 
        Smart methods perform operations in a single step so this API
 
579
        Smart methods peform operations in a single step so this api
654
580
        is not really applicable except as a compatibility thunk
655
581
        for older plugins that don't use e.g. the CommitBuilder
656
582
        facility.
670
596
        """Ensure that there is a _real_repository set.
671
597
 
672
598
        Used before calls to self._real_repository.
673
 
 
674
 
        Note that _ensure_real causes many roundtrips to the server which are
675
 
        not desirable, and prevents the use of smart one-roundtrip RPC's to
676
 
        perform complex operations (such as accessing parent data, streaming
677
 
        revisions etc). Adding calls to _ensure_real should only be done when
678
 
        bringing up new functionality, adding fallbacks for smart methods that
679
 
        require a fallback path, and never to replace an existing smart method
680
 
        invocation. If in doubt chat to the bzr network team.
681
599
        """
682
600
        if self._real_repository is None:
683
601
            self.bzrdir._ensure_real()
712
630
        self._ensure_real()
713
631
        return self._real_repository._generate_text_key_index()
714
632
 
 
633
    @symbol_versioning.deprecated_method(symbol_versioning.one_four)
 
634
    def get_revision_graph(self, revision_id=None):
 
635
        """See Repository.get_revision_graph()."""
 
636
        return self._get_revision_graph(revision_id)
 
637
 
715
638
    def _get_revision_graph(self, revision_id):
716
639
        """Private method for using with old (< 1.2) servers to fallback."""
717
640
        if revision_id is None:
772
695
        return result
773
696
 
774
697
    def has_same_location(self, other):
775
 
        return (self.__class__ is other.__class__ and
 
698
        return (self.__class__ == other.__class__ and
776
699
                self.bzrdir.transport.base == other.bzrdir.transport.base)
777
700
 
778
701
    def get_graph(self, other_repository=None):
850
773
        if not self._lock_mode:
851
774
            self._lock_mode = 'r'
852
775
            self._lock_count = 1
853
 
            self._unstacked_provider.enable_cache(cache_misses=True)
 
776
            self._unstacked_provider.enable_cache(cache_misses=False)
854
777
            if self._real_repository is not None:
855
778
                self._real_repository.lock_read()
856
779
        else:
857
780
            self._lock_count += 1
858
 
        for repo in self._fallback_repositories:
859
 
            repo.lock_read()
860
781
 
861
782
    def _remote_lock_write(self, token):
862
783
        path = self.bzrdir._path_for_remote_call(self._client)
897
818
            raise errors.ReadOnlyError(self)
898
819
        else:
899
820
            self._lock_count += 1
900
 
        for repo in self._fallback_repositories:
901
 
            # Writes don't affect fallback repos
902
 
            repo.lock_read()
903
821
        return self._lock_token or None
904
822
 
905
823
    def leave_lock_in_place(self):
927
845
        if isinstance(repository, RemoteRepository):
928
846
            raise AssertionError()
929
847
        self._real_repository = repository
930
 
        # three code paths happen here:
931
 
        # 1) old servers, RemoteBranch.open() calls _ensure_real before setting
932
 
        # up stacking. In this case self._fallback_repositories is [], and the
933
 
        # real repo is already setup. Preserve the real repo and
934
 
        # RemoteRepository.add_fallback_repository will avoid adding
935
 
        # duplicates.
936
 
        # 2) new servers, RemoteBranch.open() sets up stacking, and when
937
 
        # ensure_real is triggered from a branch, the real repository to
938
 
        # set already has a matching list with separate instances, but
939
 
        # as they are also RemoteRepositories we don't worry about making the
940
 
        # lists be identical.
941
 
        # 3) new servers, RemoteRepository.ensure_real is triggered before
942
 
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
943
 
        # and need to populate it.
944
 
        if (self._fallback_repositories and
945
 
            len(self._real_repository._fallback_repositories) !=
946
 
            len(self._fallback_repositories)):
947
 
            if len(self._real_repository._fallback_repositories):
948
 
                raise AssertionError(
949
 
                    "cannot cleanly remove existing _fallback_repositories")
950
848
        for fb in self._fallback_repositories:
951
849
            self._real_repository.add_fallback_repository(fb)
952
850
        if self._lock_mode == 'w':
959
857
    def start_write_group(self):
960
858
        """Start a write group on the decorated repository.
961
859
 
962
 
        Smart methods perform operations in a single step so this API
 
860
        Smart methods peform operations in a single step so this api
963
861
        is not really applicable except as a compatibility thunk
964
862
        for older plugins that don't use e.g. the CommitBuilder
965
863
        facility.
1002
900
            # problem releasing the vfs-based lock.
1003
901
            if old_mode == 'w':
1004
902
                # Only write-locked repositories need to make a remote method
1005
 
                # call to perform the unlock.
 
903
                # call to perfom the unlock.
1006
904
                old_token = self._lock_token
1007
905
                self._lock_token = None
1008
906
                if not self._leave_lock:
1070
968
 
1071
969
        :param repository: A repository.
1072
970
        """
1073
 
        if not self._format.supports_external_lookups:
1074
 
            raise errors.UnstackableRepositoryFormat(
1075
 
                self._format.network_name(), self.base)
 
971
        # XXX: At the moment the RemoteRepository will allow fallbacks
 
972
        # unconditionally - however, a _real_repository will usually exist,
 
973
        # and may raise an error if it's not accommodated by the underlying
 
974
        # format.  Eventually we should check when opening the repository
 
975
        # whether it's willing to allow them or not.
 
976
        #
1076
977
        # We need to accumulate additional repositories here, to pass them in
1077
978
        # on various RPC's.
1078
979
        #
1081
982
        # _real_branch had its get_stacked_on_url method called), then the
1082
983
        # repository to be added may already be in the _real_repositories list.
1083
984
        if self._real_repository is not None:
1084
 
            fallback_locations = [repo.bzrdir.root_transport.base for repo in
1085
 
                self._real_repository._fallback_repositories]
1086
 
            if repository.bzrdir.root_transport.base not in fallback_locations:
 
985
            if repository not in self._real_repository._fallback_repositories:
1087
986
                self._real_repository.add_fallback_repository(repository)
 
987
        else:
 
988
            # They are also seen by the fallback repository.  If it doesn't
 
989
            # exist yet they'll be added then.  This implicitly copies them.
 
990
            self._ensure_real()
1088
991
 
1089
992
    def add_inventory(self, revid, inv, parents):
1090
993
        self._ensure_real()
1129
1032
        self._ensure_real()
1130
1033
        return self._real_repository.make_working_trees()
1131
1034
 
1132
 
    def refresh_data(self):
1133
 
        """Re-read any data needed to to synchronise with disk.
1134
 
 
1135
 
        This method is intended to be called after another repository instance
1136
 
        (such as one used by a smart server) has inserted data into the
1137
 
        repository. It may not be called during a write group, but may be
1138
 
        called at any other time.
1139
 
        """
1140
 
        if self.is_in_write_group():
1141
 
            raise errors.InternalBzrError(
1142
 
                "May not refresh_data while in a write group.")
1143
 
        if self._real_repository is not None:
1144
 
            self._real_repository.refresh_data()
1145
 
 
1146
1035
    def revision_ids_to_search_result(self, result_set):
1147
1036
        """Convert a set of revision ids to a graph SearchResult."""
1148
1037
        result_parents = set()
1167
1056
        return repository.InterRepository.get(
1168
1057
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
1169
1058
 
1170
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1171
 
            fetch_spec=None):
1172
 
        # No base implementation to use as RemoteRepository is not a subclass
1173
 
        # of Repository; so this is a copy of Repository.fetch().
1174
 
        if fetch_spec is not None and revision_id is not None:
1175
 
            raise AssertionError(
1176
 
                "fetch_spec and revision_id are mutually exclusive.")
1177
 
        if self.is_in_write_group():
1178
 
            raise errors.InternalBzrError(
1179
 
                "May not fetch while in a write group.")
1180
 
        # fast path same-url fetch operations
1181
 
        if self.has_same_location(source) and fetch_spec is None:
 
1059
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
 
1060
        # Not delegated to _real_repository so that InterRepository.get has a
 
1061
        # chance to find an InterRepository specialised for RemoteRepository.
 
1062
        if self.has_same_location(source):
1182
1063
            # check that last_revision is in 'from' and then return a
1183
1064
            # no-operation.
1184
1065
            if (revision_id is not None and
1185
1066
                not revision.is_null(revision_id)):
1186
1067
                self.get_revision(revision_id)
1187
1068
            return 0, []
1188
 
        # if there is no specific appropriate InterRepository, this will get
1189
 
        # the InterRepository base class, which raises an
1190
 
        # IncompatibleRepositories when asked to fetch.
1191
1069
        inter = repository.InterRepository.get(source, self)
1192
 
        return inter.fetch(revision_id=revision_id, pb=pb,
1193
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1070
        try:
 
1071
            return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
 
1072
        except NotImplementedError:
 
1073
            raise errors.IncompatibleRepositories(source, self)
1194
1074
 
1195
1075
    def create_bundle(self, target, base, fileobj, format=None):
1196
1076
        self._ensure_real()
1227
1107
            # We already found out that the server can't understand
1228
1108
            # Repository.get_parent_map requests, so just fetch the whole
1229
1109
            # graph.
1230
 
            #
1231
 
            # Note that this reads the whole graph, when only some keys are
1232
 
            # wanted.  On this old server there's no way (?) to get them all
1233
 
            # in one go, and the user probably will have seen a warning about
1234
 
            # the server being old anyhow.
1235
 
            rg = self._get_revision_graph(None)
1236
 
            # There is an API discrepancy between get_parent_map and
 
1110
            # XXX: Note that this will issue a deprecation warning. This is ok
 
1111
            # :- its because we're working with a deprecated server anyway, and
 
1112
            # the user will almost certainly have seen a warning about the
 
1113
            # server version already.
 
1114
            rg = self.get_revision_graph()
 
1115
            # There is an api discrepency between get_parent_map and
1237
1116
            # get_revision_graph. Specifically, a "key:()" pair in
1238
1117
            # get_revision_graph just means a node has no parents. For
1239
1118
            # "get_parent_map" it means the node is a ghost. So fix up the
1269
1148
        # TODO: Manage this incrementally to avoid covering the same path
1270
1149
        # repeatedly. (The server will have to on each request, but the less
1271
1150
        # work done the better).
1272
 
        #
1273
 
        # Negative caching notes:
1274
 
        # new server sends missing when a request including the revid
1275
 
        # 'include-missing:' is present in the request.
1276
 
        # missing keys are serialised as missing:X, and we then call
1277
 
        # provider.note_missing(X) for-all X
1278
1151
        parents_map = self._unstacked_provider.get_cached_map()
1279
1152
        if parents_map is None:
1280
1153
            # Repository is not locked, so there's no cache.
1281
1154
            parents_map = {}
1282
 
        # start_set is all the keys in the cache
1283
1155
        start_set = set(parents_map)
1284
 
        # result set is all the references to keys in the cache
1285
1156
        result_parents = set()
1286
1157
        for parents in parents_map.itervalues():
1287
1158
            result_parents.update(parents)
1288
1159
        stop_keys = result_parents.difference(start_set)
1289
 
        # We don't need to send ghosts back to the server as a position to
1290
 
        # stop either.
1291
 
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
1292
 
        key_count = len(parents_map)
1293
 
        if (NULL_REVISION in result_parents
1294
 
            and NULL_REVISION in self._unstacked_provider.missing_keys):
1295
 
            # If we pruned NULL_REVISION from the stop_keys because it's also
1296
 
            # in our cache of "missing" keys we need to increment our key count
1297
 
            # by 1, because the reconsitituted SearchResult on the server will
1298
 
            # still consider NULL_REVISION to be an included key.
1299
 
            key_count += 1
1300
1160
        included_keys = start_set.intersection(result_parents)
1301
1161
        start_set.difference_update(included_keys)
1302
 
        recipe = ('manual', start_set, stop_keys, key_count)
 
1162
        recipe = (start_set, stop_keys, len(parents_map))
1303
1163
        body = self._serialise_search_recipe(recipe)
1304
1164
        path = self.bzrdir._path_for_remote_call(self._client)
1305
1165
        for key in keys:
1307
1167
                raise ValueError(
1308
1168
                    "key %r not a plain string" % (key,))
1309
1169
        verb = 'Repository.get_parent_map'
1310
 
        args = (path, 'include-missing:') + tuple(keys)
 
1170
        args = (path,) + tuple(keys)
1311
1171
        try:
1312
1172
            response = self._call_with_body_bytes_expecting_body(
1313
1173
                verb, args, body)
1323
1183
            # To avoid having to disconnect repeatedly, we keep track of the
1324
1184
            # fact the server doesn't understand remote methods added in 1.2.
1325
1185
            medium._remember_remote_is_before((1, 2))
1326
 
            # Recurse just once and we should use the fallback code.
1327
 
            return self._get_parent_map_rpc(keys)
 
1186
            return self.get_revision_graph(None)
1328
1187
        response_tuple, response_handler = response
1329
1188
        if response_tuple[0] not in ['ok']:
1330
1189
            response_handler.cancel_read_body()
1341
1200
                if len(d) > 1:
1342
1201
                    revision_graph[d[0]] = d[1:]
1343
1202
                else:
1344
 
                    # No parents:
1345
 
                    if d[0].startswith('missing:'):
1346
 
                        revid = d[0][8:]
1347
 
                        self._unstacked_provider.note_missing_key(revid)
1348
 
                    else:
1349
 
                        # no parents - so give the Graph result
1350
 
                        # (NULL_REVISION,).
1351
 
                        revision_graph[d[0]] = (NULL_REVISION,)
 
1203
                    # No parents - so give the Graph result (NULL_REVISION,).
 
1204
                    revision_graph[d[0]] = (NULL_REVISION,)
1352
1205
            return revision_graph
1353
1206
 
1354
1207
    @needs_read_lock
1357
1210
        return self._real_repository.get_signature_text(revision_id)
1358
1211
 
1359
1212
    @needs_read_lock
 
1213
    @symbol_versioning.deprecated_method(symbol_versioning.one_three)
 
1214
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
1215
        self._ensure_real()
 
1216
        return self._real_repository.get_revision_graph_with_ghosts(
 
1217
            revision_ids=revision_ids)
 
1218
 
 
1219
    @needs_read_lock
1360
1220
    def get_inventory_xml(self, revision_id):
1361
1221
        self._ensure_real()
1362
1222
        return self._real_repository.get_inventory_xml(revision_id)
1374
1234
        return self._real_repository.all_revision_ids()
1375
1235
 
1376
1236
    @needs_read_lock
1377
 
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
 
1237
    def get_deltas_for_revisions(self, revisions):
1378
1238
        self._ensure_real()
1379
 
        return self._real_repository.get_deltas_for_revisions(revisions,
1380
 
            specific_fileids=specific_fileids)
 
1239
        return self._real_repository.get_deltas_for_revisions(revisions)
1381
1240
 
1382
1241
    @needs_read_lock
1383
 
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1242
    def get_revision_delta(self, revision_id):
1384
1243
        self._ensure_real()
1385
 
        return self._real_repository.get_revision_delta(revision_id,
1386
 
            specific_fileids=specific_fileids)
 
1244
        return self._real_repository.get_revision_delta(revision_id)
1387
1245
 
1388
1246
    @needs_read_lock
1389
1247
    def revision_trees(self, revision_ids):
1566
1424
        :param recipe: A search recipe (start, stop, count).
1567
1425
        :return: Serialised bytes.
1568
1426
        """
1569
 
        start_keys = ' '.join(recipe[1])
1570
 
        stop_keys = ' '.join(recipe[2])
1571
 
        count = str(recipe[3])
 
1427
        start_keys = ' '.join(recipe[0])
 
1428
        stop_keys = ' '.join(recipe[1])
 
1429
        count = str(recipe[2])
1572
1430
        return '\n'.join((start_keys, stop_keys, count))
1573
1431
 
1574
 
    def _serialise_search_result(self, search_result):
1575
 
        if isinstance(search_result, graph.PendingAncestryResult):
1576
 
            parts = ['ancestry-of']
1577
 
            parts.extend(search_result.heads)
1578
 
        else:
1579
 
            recipe = search_result.get_recipe()
1580
 
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
1581
 
        return '\n'.join(parts)
1582
 
 
1583
1432
    def autopack(self):
1584
1433
        path = self.bzrdir._path_for_remote_call(self._client)
1585
1434
        try:
1588
1437
            self._ensure_real()
1589
1438
            self._real_repository._pack_collection.autopack()
1590
1439
            return
1591
 
        self.refresh_data()
 
1440
        if self._real_repository is not None:
 
1441
            # Reset the real repository's cache of pack names.
 
1442
            # XXX: At some point we may be able to skip this and just rely on
 
1443
            # the automatic retry logic to do the right thing, but for now we
 
1444
            # err on the side of being correct rather than being optimal.
 
1445
            self._real_repository._pack_collection.reload_pack_names()
1592
1446
        if response[0] != 'ok':
1593
1447
            raise errors.UnexpectedSmartServerResponse(response)
1594
1448
 
1604
1458
        return result
1605
1459
 
1606
1460
    def insert_stream(self, stream, src_format, resume_tokens):
1607
 
        target = self.target_repo
1608
 
        if target._lock_token:
1609
 
            verb = 'Repository.insert_stream_locked'
1610
 
            extra_args = (target._lock_token or '',)
1611
 
            required_version = (1, 14)
1612
 
        else:
1613
 
            verb = 'Repository.insert_stream'
1614
 
            extra_args = ()
1615
 
            required_version = (1, 13)
1616
 
        client = target._client
 
1461
        repo = self.target_repo
 
1462
        client = repo._client
1617
1463
        medium = client._medium
1618
 
        if medium._is_remote_before(required_version):
 
1464
        if medium._is_remote_before((1, 13)):
1619
1465
            # No possible way this can work.
1620
1466
            return self._insert_real(stream, src_format, resume_tokens)
1621
 
        path = target.bzrdir._path_for_remote_call(client)
 
1467
        path = repo.bzrdir._path_for_remote_call(client)
1622
1468
        if not resume_tokens:
1623
1469
            # XXX: Ugly but important for correctness, *will* be fixed during
1624
1470
            # 1.13 cycle. Pushing a stream that is interrupted results in a
1631
1477
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1632
1478
            try:
1633
1479
                response = client.call_with_body_stream(
1634
 
                    (verb, path, '') + extra_args, byte_stream)
 
1480
                    ('Repository.insert_stream', path, ''), byte_stream)
1635
1481
            except errors.UnknownSmartMethod:
1636
 
                medium._remember_remote_is_before(required_version)
 
1482
                medium._remember_remote_is_before((1,13))
1637
1483
                return self._insert_real(stream, src_format, resume_tokens)
1638
1484
        byte_stream = smart_repo._stream_to_byte_stream(
1639
1485
            stream, src_format)
1640
1486
        resume_tokens = ' '.join(resume_tokens)
1641
1487
        response = client.call_with_body_stream(
1642
 
            (verb, path, resume_tokens) + extra_args, byte_stream)
 
1488
            ('Repository.insert_stream', path, resume_tokens), byte_stream)
1643
1489
        if response[0][0] not in ('ok', 'missing-basis'):
1644
1490
            raise errors.UnexpectedSmartServerResponse(response)
1645
1491
        if response[0][0] == 'missing-basis':
1647
1493
            resume_tokens = tokens
1648
1494
            return resume_tokens, missing_keys
1649
1495
        else:
1650
 
            self.target_repo.refresh_data()
 
1496
            if self.target_repo._real_repository is not None:
 
1497
                collection = getattr(self.target_repo._real_repository,
 
1498
                    '_pack_collection', None)
 
1499
                if collection is not None:
 
1500
                    collection.reload_pack_names()
1651
1501
            return [], set()
1652
1502
 
1653
1503
 
1655
1505
    """Stream data from a remote server."""
1656
1506
 
1657
1507
    def get_stream(self, search):
1658
 
        if (self.from_repository._fallback_repositories and
1659
 
            self.to_format._fetch_order == 'topological'):
1660
 
            return self._real_stream(self.from_repository, search)
1661
 
        return self.missing_parents_chain(search, [self.from_repository] +
1662
 
            self.from_repository._fallback_repositories)
1663
 
 
1664
 
    def _real_stream(self, repo, search):
1665
 
        """Get a stream for search from repo.
1666
 
        
1667
 
        This never called RemoteStreamSource.get_stream, and is a heler
1668
 
        for RemoteStreamSource._get_stream to allow getting a stream 
1669
 
        reliably whether fallback back because of old servers or trying
1670
 
        to stream from a non-RemoteRepository (which the stacked support
1671
 
        code will do).
1672
 
        """
1673
 
        source = repo._get_source(self.to_format)
1674
 
        if isinstance(source, RemoteStreamSource):
1675
 
            return repository.StreamSource.get_stream(source, search)
1676
 
        return source.get_stream(search)
1677
 
 
1678
 
    def _get_stream(self, repo, search):
1679
 
        """Core worker to get a stream from repo for search.
1680
 
 
1681
 
        This is used by both get_stream and the stacking support logic. It
1682
 
        deliberately gets a stream for repo which does not need to be
1683
 
        self.from_repository. In the event that repo is not Remote, or
1684
 
        cannot do a smart stream, a fallback is made to the generic
1685
 
        repository._get_stream() interface, via self._real_stream.
1686
 
 
1687
 
        In the event of stacking, streams from _get_stream will not
1688
 
        contain all the data for search - this is normal (see get_stream).
1689
 
 
1690
 
        :param repo: A repository.
1691
 
        :param search: A search.
1692
 
        """
1693
 
        # Fallbacks may be non-smart
1694
 
        if not isinstance(repo, RemoteRepository):
1695
 
            return self._real_stream(repo, search)
 
1508
        # streaming with fallback repositories is not well defined yet: The
 
1509
        # remote repository cannot see the fallback repositories, and thus
 
1510
        # cannot satisfy the entire search in the general case. Likewise the
 
1511
        # fallback repositories cannot reify the search to determine what they
 
1512
        # should send. It likely needs a return value in the stream listing the
 
1513
        # edge of the search to resume from in fallback repositories.
 
1514
        if self.from_repository._fallback_repositories:
 
1515
            return repository.StreamSource.get_stream(self, search)
 
1516
        repo = self.from_repository
1696
1517
        client = repo._client
1697
1518
        medium = client._medium
1698
1519
        if medium._is_remote_before((1, 13)):
1699
 
            # streaming was added in 1.13
1700
 
            return self._real_stream(repo, search)
 
1520
            # No possible way this can work.
 
1521
            return repository.StreamSource.get_stream(self, search)
1701
1522
        path = repo.bzrdir._path_for_remote_call(client)
1702
1523
        try:
1703
 
            search_bytes = repo._serialise_search_result(search)
 
1524
            recipe = repo._serialise_search_recipe(search._recipe)
1704
1525
            response = repo._call_with_body_bytes_expecting_body(
1705
1526
                'Repository.get_stream',
1706
 
                (path, self.to_format.network_name()), search_bytes)
 
1527
                (path, self.to_format.network_name()), recipe)
1707
1528
            response_tuple, response_handler = response
1708
1529
        except errors.UnknownSmartMethod:
1709
1530
            medium._remember_remote_is_before((1,13))
1710
 
            return self._real_stream(repo, search)
 
1531
            return repository.StreamSource.get_stream(self, search)
1711
1532
        if response_tuple[0] != 'ok':
1712
1533
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1713
1534
        byte_stream = response_handler.read_streamed_body()
1718
1539
                src_format.network_name(), repo._format.network_name()))
1719
1540
        return stream
1720
1541
 
1721
 
    def missing_parents_chain(self, search, sources):
1722
 
        """Chain multiple streams together to handle stacking.
1723
 
 
1724
 
        :param search: The overall search to satisfy with streams.
1725
 
        :param sources: A list of Repository objects to query.
1726
 
        """
1727
 
        self.serialiser = self.to_format._serializer
1728
 
        self.seen_revs = set()
1729
 
        self.referenced_revs = set()
1730
 
        # If there are heads in the search, or the key count is > 0, we are not
1731
 
        # done.
1732
 
        while not search.is_empty() and len(sources) > 1:
1733
 
            source = sources.pop(0)
1734
 
            stream = self._get_stream(source, search)
1735
 
            for kind, substream in stream:
1736
 
                if kind != 'revisions':
1737
 
                    yield kind, substream
1738
 
                else:
1739
 
                    yield kind, self.missing_parents_rev_handler(substream)
1740
 
            search = search.refine(self.seen_revs, self.referenced_revs)
1741
 
            self.seen_revs = set()
1742
 
            self.referenced_revs = set()
1743
 
        if not search.is_empty():
1744
 
            for kind, stream in self._get_stream(sources[0], search):
1745
 
                yield kind, stream
1746
 
 
1747
 
    def missing_parents_rev_handler(self, substream):
1748
 
        for content in substream:
1749
 
            revision_bytes = content.get_bytes_as('fulltext')
1750
 
            revision = self.serialiser.read_revision_from_string(revision_bytes)
1751
 
            self.seen_revs.add(content.key[-1])
1752
 
            self.referenced_revs.update(revision.parent_ids)
1753
 
            yield content
1754
 
 
1755
1542
 
1756
1543
class RemoteBranchLockableFiles(LockableFiles):
1757
1544
    """A 'LockableFiles' implementation that talks to a smart server.
1775
1562
 
1776
1563
class RemoteBranchFormat(branch.BranchFormat):
1777
1564
 
1778
 
    def __init__(self, network_name=None):
 
1565
    def __init__(self):
1779
1566
        super(RemoteBranchFormat, self).__init__()
1780
1567
        self._matchingbzrdir = RemoteBzrDirFormat()
1781
1568
        self._matchingbzrdir.set_branch_format(self)
1782
1569
        self._custom_format = None
1783
 
        self._network_name = network_name
1784
1570
 
1785
1571
    def __eq__(self, other):
1786
1572
        return (isinstance(other, RemoteBranchFormat) and
1787
1573
            self.__dict__ == other.__dict__)
1788
1574
 
1789
 
    def _ensure_real(self):
1790
 
        if self._custom_format is None:
1791
 
            self._custom_format = branch.network_format_registry.get(
1792
 
                self._network_name)
1793
 
 
1794
1575
    def get_format_description(self):
1795
1576
        return 'Remote BZR Branch'
1796
1577
 
1797
1578
    def network_name(self):
1798
1579
        return self._network_name
1799
1580
 
1800
 
    def open(self, a_bzrdir, ignore_fallbacks=False):
1801
 
        return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
 
1581
    def open(self, a_bzrdir):
 
1582
        return a_bzrdir.open_branch()
1802
1583
 
1803
1584
    def _vfs_initialize(self, a_bzrdir):
1804
1585
        # Initialisation when using a local bzrdir object, or a non-vfs init
1840
1621
            response = a_bzrdir._call(verb, path, network_name)
1841
1622
        except errors.UnknownSmartMethod:
1842
1623
            # Fallback - use vfs methods
1843
 
            medium._remember_remote_is_before((1, 13))
1844
1624
            return self._vfs_initialize(a_bzrdir)
1845
1625
        if response[0] != 'ok':
1846
1626
            raise errors.UnexpectedSmartServerResponse(response)
1847
1627
        # Turn the response into a RemoteRepository object.
1848
 
        format = RemoteBranchFormat(network_name=response[1])
 
1628
        format = RemoteBranchFormat()
 
1629
        format._network_name = response[1]
1849
1630
        repo_format = response_tuple_to_repo_format(response[3:])
1850
1631
        if response[2] == '':
1851
1632
            repo_bzrdir = a_bzrdir
1862
1643
        remote_branch._last_revision_info_cache = 0, NULL_REVISION
1863
1644
        return remote_branch
1864
1645
 
1865
 
    def make_tags(self, branch):
1866
 
        self._ensure_real()
1867
 
        return self._custom_format.make_tags(branch)
1868
 
 
1869
1646
    def supports_tags(self):
1870
1647
        # Remote branches might support tags, but we won't know until we
1871
1648
        # access the real remote branch.
1872
 
        self._ensure_real()
1873
 
        return self._custom_format.supports_tags()
1874
 
 
1875
 
    def supports_stacking(self):
1876
 
        self._ensure_real()
1877
 
        return self._custom_format.supports_stacking()
 
1649
        return True
1878
1650
 
1879
1651
 
1880
1652
class RemoteBranch(branch.Branch, _RpcHelper):
1923
1695
            self._real_branch.repository = self.repository
1924
1696
        else:
1925
1697
            self._real_branch = None
1926
 
        # Fill out expected attributes of branch for bzrlib API users.
 
1698
        # Fill out expected attributes of branch for bzrlib api users.
1927
1699
        self.base = self.bzrdir.root_transport.base
1928
1700
        self._control_files = None
1929
1701
        self._lock_mode = None
1939
1711
            if real_branch is not None:
1940
1712
                self._format._network_name = \
1941
1713
                    self._real_branch._format.network_name()
 
1714
            #else:
 
1715
            #    # XXX: Need to get this from BzrDir.open_branch's return value.
 
1716
            #    self._ensure_real()
 
1717
            #    self._format._network_name = \
 
1718
            #        self._real_branch._format.network_name()
1942
1719
        else:
1943
1720
            self._format = format
1944
 
        if not self._format._network_name:
1945
 
            # Did not get from open_branchV2 - old server.
1946
 
            self._ensure_real()
1947
 
            self._format._network_name = \
1948
 
                self._real_branch._format.network_name()
1949
 
        self.tags = self._format.make_tags(self)
1950
1721
        # The base class init is not called, so we duplicate this:
1951
1722
        hooks = branch.Branch.hooks['open']
1952
1723
        for hook in hooks:
1962
1733
        except (errors.NotStacked, errors.UnstackableBranchFormat,
1963
1734
            errors.UnstackableRepositoryFormat), e:
1964
1735
            return
1965
 
        self._activate_fallback_location(fallback_url)
1966
 
 
1967
 
    def _get_config(self):
1968
 
        return RemoteBranchConfig(self)
 
1736
        # it's relative to this branch...
 
1737
        fallback_url = urlutils.join(self.base, fallback_url)
 
1738
        transports = [self.bzrdir.root_transport]
 
1739
        if self._real_branch is not None:
 
1740
            # The real repository is setup already:
 
1741
            transports.append(self._real_branch._transport)
 
1742
            self.repository.add_fallback_repository(
 
1743
                self.repository._real_repository._fallback_repositories[0])
 
1744
        else:
 
1745
            stacked_on = branch.Branch.open(fallback_url,
 
1746
                                            possible_transports=transports)
 
1747
            self.repository.add_fallback_repository(stacked_on.repository)
1969
1748
 
1970
1749
    def _get_real_transport(self):
1971
1750
        # if we try vfs access, return the real branch's vfs transport
2069
1848
            raise errors.UnexpectedSmartServerResponse(response)
2070
1849
        return response[1]
2071
1850
 
2072
 
    def _vfs_get_tags_bytes(self):
2073
 
        self._ensure_real()
2074
 
        return self._real_branch._get_tags_bytes()
2075
 
 
2076
 
    def _get_tags_bytes(self):
2077
 
        medium = self._client._medium
2078
 
        if medium._is_remote_before((1, 13)):
2079
 
            return self._vfs_get_tags_bytes()
2080
 
        try:
2081
 
            response = self._call('Branch.get_tags_bytes', self._remote_path())
2082
 
        except errors.UnknownSmartMethod:
2083
 
            medium._remember_remote_is_before((1, 13))
2084
 
            return self._vfs_get_tags_bytes()
2085
 
        return response[0]
2086
 
 
2087
1851
    def lock_read(self):
2088
1852
        self.repository.lock_read()
2089
1853
        if not self._lock_mode:
2143
1907
            self.repository.lock_write(self._repo_lock_token)
2144
1908
        return self._lock_token or None
2145
1909
 
2146
 
    def _set_tags_bytes(self, bytes):
2147
 
        self._ensure_real()
2148
 
        return self._real_branch._set_tags_bytes(bytes)
2149
 
 
2150
1910
    def _unlock(self, branch_token, repo_token):
2151
1911
        err_context = {'token': str((branch_token, repo_token))}
2152
1912
        response = self._call(
2174
1934
                    self._real_branch.unlock()
2175
1935
                if mode != 'w':
2176
1936
                    # Only write-locked branched need to make a remote method
2177
 
                    # call to perform the unlock.
 
1937
                    # call to perfom the unlock.
2178
1938
                    return
2179
1939
                if not self._lock_token:
2180
1940
                    raise AssertionError('Locked, but no token!')
2288
2048
        try:
2289
2049
            response = self._call('Branch.get_parent', self._remote_path())
2290
2050
        except errors.UnknownSmartMethod:
2291
 
            medium._remember_remote_is_before((1, 13))
2292
2051
            return self._vfs_get_parent_location()
2293
2052
        if len(response) != 1:
2294
2053
            raise errors.UnexpectedSmartServerResponse(response)
2313
2072
            self._ensure_real()
2314
2073
            return self._real_branch._set_parent_location(url)
2315
2074
 
 
2075
    def set_stacked_on_url(self, stacked_location):
 
2076
        """Set the URL this branch is stacked against.
 
2077
 
 
2078
        :raises UnstackableBranchFormat: If the branch does not support
 
2079
            stacking.
 
2080
        :raises UnstackableRepositoryFormat: If the repository does not support
 
2081
            stacking.
 
2082
        """
 
2083
        self._ensure_real()
 
2084
        return self._real_branch.set_stacked_on_url(stacked_location)
 
2085
 
2316
2086
    @needs_write_lock
2317
2087
    def pull(self, source, overwrite=False, stop_revision=None,
2318
2088
             **kwargs):
2380
2150
        self.set_revision_history(self._lefthand_history(revision_id,
2381
2151
            last_rev=last_rev,other_branch=other_branch))
2382
2152
 
 
2153
    @property
 
2154
    def tags(self):
 
2155
        self._ensure_real()
 
2156
        return self._real_branch.tags
 
2157
 
2383
2158
    def set_push_location(self, location):
2384
2159
        self._ensure_real()
2385
2160
        return self._real_branch.set_push_location(location)
2386
2161
 
2387
2162
 
2388
 
class RemoteBranchConfig(object):
2389
 
    """A Config that reads from a smart branch and writes via smart methods.
2390
 
 
2391
 
    It is a low-level object that considers config data to be name/value pairs
2392
 
    that may be associated with a section. Assigning meaning to the these
2393
 
    values is done at higher levels like bzrlib.config.TreeConfig.
2394
 
    """
2395
 
 
2396
 
    def __init__(self, branch):
2397
 
        self._branch = branch
2398
 
 
2399
 
    def get_option(self, name, section=None, default=None):
2400
 
        """Return the value associated with a named option.
2401
 
 
2402
 
        :param name: The name of the value
2403
 
        :param section: The section the option is in (if any)
2404
 
        :param default: The value to return if the value is not set
2405
 
        :return: The value or default value
2406
 
        """
2407
 
        configobj = self._get_configobj()
2408
 
        if section is None:
2409
 
            section_obj = configobj
2410
 
        else:
2411
 
            try:
2412
 
                section_obj = configobj[section]
2413
 
            except KeyError:
2414
 
                return default
2415
 
        return section_obj.get(name, default)
2416
 
 
2417
 
    def _get_configobj(self):
2418
 
        path = self._branch._remote_path()
2419
 
        response = self._branch._client.call_expecting_body(
2420
 
            'Branch.get_config_file', path)
2421
 
        if response[0][0] != 'ok':
2422
 
            raise UnexpectedSmartServerResponse(response)
2423
 
        lines = response[1].read_body_bytes().splitlines()
2424
 
        return config.ConfigObj(lines, encoding='utf-8')
2425
 
 
2426
 
    def set_option(self, value, name, section=None):
2427
 
        """Set the value associated with a named option.
2428
 
 
2429
 
        :param value: The value to set
2430
 
        :param name: The name of the value to set
2431
 
        :param section: The section the option is in (if any)
2432
 
        """
2433
 
        medium = self._branch._client._medium
2434
 
        if medium._is_remote_before((1, 14)):
2435
 
            return self._vfs_set_option(value, name, section)
2436
 
        try:
2437
 
            path = self._branch._remote_path()
2438
 
            response = self._branch._client.call('Branch.set_config_option',
2439
 
                path, self._branch._lock_token, self._branch._repo_lock_token,
2440
 
                value.encode('utf8'), name, section or '')
2441
 
        except errors.UnknownSmartMethod:
2442
 
            medium._remember_remote_is_before((1, 14))
2443
 
            return self._vfs_set_option(value, name, section)
2444
 
        if response != ():
2445
 
            raise errors.UnexpectedSmartServerResponse(response)
2446
 
 
2447
 
    def _vfs_set_option(self, value, name, section=None):
2448
 
        self._branch._ensure_real()
2449
 
        return self._branch._real_branch._get_config().set_option(
2450
 
            value, name, section)
2451
 
 
2452
 
 
2453
2163
def _extract_tar(tar, to_dir):
2454
2164
    """Extract all the contents of a tarfile object.
2455
2165