70
65
method, args, body_bytes)
71
66
except errors.ErrorFromSmartServer, err:
72
67
self._translate_error(err, **err_context)
75
def response_tuple_to_repo_format(response):
76
"""Convert a response tuple describing a repository format to a format."""
77
format = RemoteRepositoryFormat()
78
format._rich_root_data = (response[0] == 'yes')
79
format._supports_tree_reference = (response[1] == 'yes')
80
format._supports_external_lookups = (response[2] == 'yes')
81
format._network_name = response[3]
85
69
# Note: RemoteBzrDirFormat is in bzrdir.py
87
71
class RemoteBzrDir(BzrDir, _RpcHelper):
88
72
"""Control directory on a remote server, accessed via bzr:// or similar."""
90
def __init__(self, transport, format, _client=None):
74
def __init__(self, transport, _client=None):
91
75
"""Construct a RemoteBzrDir.
93
77
:param _client: Private parameter for testing. Disables probing and the
94
78
use of a real bzrdir.
96
BzrDir.__init__(self, transport, format)
80
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
97
81
# this object holds a delegated bzrdir that uses file-level operations
98
82
# to talk to the other side
99
83
self._real_bzrdir = None
100
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
101
# create_branch for details.
102
self._next_open_branch_result = None
104
85
if _client is None:
105
86
medium = transport.get_smart_medium()
123
104
if not self._real_bzrdir:
124
105
self._real_bzrdir = BzrDir.open_from_transport(
125
106
self.root_transport, _server_formats=False)
126
self._format._network_name = \
127
self._real_bzrdir._format.network_name()
129
108
def _translate_error(self, err, **context):
130
109
_translate_error(err, bzrdir=self, **context)
132
def break_lock(self):
133
# Prevent aliasing problems in the next_open_branch_result cache.
134
# See create_branch for rationale.
135
self._next_open_branch_result = None
136
return BzrDir.break_lock(self)
138
def _vfs_cloning_metadir(self, require_stacking=False):
111
def cloning_metadir(self, stacked=False):
139
112
self._ensure_real()
140
return self._real_bzrdir.cloning_metadir(
141
require_stacking=require_stacking)
143
def cloning_metadir(self, require_stacking=False):
144
medium = self._client._medium
145
if medium._is_remote_before((1, 13)):
146
return self._vfs_cloning_metadir(require_stacking=require_stacking)
147
verb = 'BzrDir.cloning_metadir'
152
path = self._path_for_remote_call(self._client)
154
response = self._call(verb, path, stacking)
155
except errors.UnknownSmartMethod:
156
medium._remember_remote_is_before((1, 13))
157
return self._vfs_cloning_metadir(require_stacking=require_stacking)
158
except errors.UnknownErrorFromSmartServer, err:
159
if err.error_tuple != ('BranchReference',):
161
# We need to resolve the branch reference to determine the
162
# cloning_metadir. This causes unnecessary RPCs to open the
163
# referenced branch (and bzrdir, etc) but only when the caller
164
# didn't already resolve the branch reference.
165
referenced_branch = self.open_branch()
166
return referenced_branch.bzrdir.cloning_metadir()
167
if len(response) != 3:
168
raise errors.UnexpectedSmartServerResponse(response)
169
control_name, repo_name, branch_info = response
170
if len(branch_info) != 2:
171
raise errors.UnexpectedSmartServerResponse(response)
172
branch_ref, branch_name = branch_info
173
format = bzrdir.network_format_registry.get(control_name)
175
format.repository_format = repository.network_format_registry.get(
177
if branch_ref == 'ref':
178
# XXX: we need possible_transports here to avoid reopening the
179
# connection to the referenced location
180
ref_bzrdir = BzrDir.open(branch_name)
181
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
182
format.set_branch_format(branch_format)
183
elif branch_ref == 'branch':
185
format.set_branch_format(
186
branch.network_format_registry.get(branch_name))
188
raise errors.UnexpectedSmartServerResponse(response)
113
return self._real_bzrdir.cloning_metadir(stacked)
191
115
def create_repository(self, shared=False):
192
# as per meta1 formats - just delegate to the format object which may
194
result = self._format.repository_format.initialize(self, shared)
195
if not isinstance(result, RemoteRepository):
196
return self.open_repository()
117
self._real_bzrdir.create_repository(shared=shared)
118
return self.open_repository()
200
120
def destroy_repository(self):
201
121
"""See BzrDir.destroy_repository"""
239
146
def get_branch_reference(self):
240
147
"""See BzrDir.get_branch_reference()."""
241
response = self._get_branch_reference()
242
if response[0] == 'ref':
247
def _get_branch_reference(self):
248
148
path = self._path_for_remote_call(self._client)
249
medium = self._client._medium
250
if not medium._is_remote_before((1, 13)):
252
response = self._call('BzrDir.open_branchV2', path)
253
if response[0] not in ('ref', 'branch'):
254
raise errors.UnexpectedSmartServerResponse(response)
256
except errors.UnknownSmartMethod:
257
medium._remember_remote_is_before((1, 13))
258
149
response = self._call('BzrDir.open_branch', path)
259
if response[0] != 'ok':
150
if response[0] == 'ok':
151
if response[1] == '':
152
# branch at this location.
155
# a branch reference, use the existing BranchReference logic.
260
158
raise errors.UnexpectedSmartServerResponse(response)
261
if response[1] != '':
262
return ('ref', response[1])
264
return ('branch', '')
266
160
def _get_tree_branch(self):
267
161
"""See BzrDir._get_tree_branch()."""
268
162
return None, self.open_branch()
270
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
164
def open_branch(self, _unsupported=False):
272
166
raise NotImplementedError('unsupported flag support not implemented yet.')
273
if self._next_open_branch_result is not None:
274
# See create_branch for details.
275
result = self._next_open_branch_result
276
self._next_open_branch_result = None
278
response = self._get_branch_reference()
279
if response[0] == 'ref':
167
reference_url = self.get_branch_reference()
168
if reference_url is None:
169
# branch at this location.
170
return RemoteBranch(self, self.find_repository())
280
172
# a branch reference, use the existing BranchReference logic.
281
173
format = BranchReferenceFormat()
282
return format.open(self, _found=True, location=response[1],
283
ignore_fallbacks=ignore_fallbacks)
284
branch_format_name = response[1]
285
if not branch_format_name:
286
branch_format_name = None
287
format = RemoteBranchFormat(network_name=branch_format_name)
288
return RemoteBranch(self, self.find_repository(), format=format,
289
setup_stacking=not ignore_fallbacks)
291
def _open_repo_v1(self, path):
292
verb = 'BzrDir.find_repository'
293
response = self._call(verb, path)
294
if response[0] != 'ok':
295
raise errors.UnexpectedSmartServerResponse(response)
296
# servers that only support the v1 method don't support external
299
repo = self._real_bzrdir.open_repository()
300
response = response + ('no', repo._format.network_name())
301
return response, repo
303
def _open_repo_v2(self, path):
174
return format.open(self, _found=True, location=reference_url)
176
def open_repository(self):
177
path = self._path_for_remote_call(self._client)
304
178
verb = 'BzrDir.find_repositoryV2'
305
response = self._call(verb, path)
306
if response[0] != 'ok':
307
raise errors.UnexpectedSmartServerResponse(response)
309
repo = self._real_bzrdir.open_repository()
310
response = response + (repo._format.network_name(),)
311
return response, repo
313
def _open_repo_v3(self, path):
314
verb = 'BzrDir.find_repositoryV3'
315
medium = self._client._medium
316
if medium._is_remote_before((1, 13)):
317
raise errors.UnknownSmartMethod(verb)
319
180
response = self._call(verb, path)
320
181
except errors.UnknownSmartMethod:
321
medium._remember_remote_is_before((1, 13))
323
if response[0] != 'ok':
324
raise errors.UnexpectedSmartServerResponse(response)
325
return response, None
327
def open_repository(self):
328
path = self._path_for_remote_call(self._client)
330
for probe in [self._open_repo_v3, self._open_repo_v2,
333
response, real_repo = probe(path)
335
except errors.UnknownSmartMethod:
338
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
339
if response[0] != 'ok':
340
raise errors.UnexpectedSmartServerResponse(response)
341
if len(response) != 6:
182
verb = 'BzrDir.find_repository'
183
response = self._call(verb, path)
184
if response[0] != 'ok':
185
raise errors.UnexpectedSmartServerResponse(response)
186
if verb == 'BzrDir.find_repository':
187
# servers that don't support the V2 method don't support external
189
response = response + ('no', )
190
if not (len(response) == 5):
342
191
raise SmartProtocolError('incorrect response length %s' % (response,))
343
192
if response[1] == '':
344
# repo is at this dir.
345
format = response_tuple_to_repo_format(response[2:])
193
format = RemoteRepositoryFormat()
194
format.rich_root_data = (response[2] == 'yes')
195
format.supports_tree_reference = (response[3] == 'yes')
196
# No wire format to check this yet.
197
format.supports_external_lookups = (response[4] == 'yes')
346
198
# Used to support creating a real format instance when needed.
347
199
format._creating_bzrdir = self
348
remote_repo = RemoteRepository(self, format)
349
format._creating_repo = remote_repo
350
if real_repo is not None:
351
remote_repo._set_real_repository(real_repo)
200
return RemoteRepository(self, format)
354
202
raise errors.NoRepositoryPresent(self)
408
254
the attributes rich_root_data and supports_tree_reference are set
409
255
on a per instance basis, and are not set (and should not be) at
412
:ivar _custom_format: If set, a specific concrete repository format that
413
will be used when initializing a repository with this
414
RemoteRepositoryFormat.
415
:ivar _creating_repo: If set, the repository object that this
416
RemoteRepositoryFormat was created for: it can be called into
417
to obtain data like the network name.
420
259
_matchingbzrdir = RemoteBzrDirFormat()
423
repository.RepositoryFormat.__init__(self)
424
self._custom_format = None
425
self._network_name = None
426
self._creating_bzrdir = None
427
self._supports_external_lookups = None
428
self._supports_tree_reference = None
429
self._rich_root_data = None
432
def fast_deltas(self):
434
return self._custom_format.fast_deltas
437
def rich_root_data(self):
438
if self._rich_root_data is None:
440
self._rich_root_data = self._custom_format.rich_root_data
441
return self._rich_root_data
444
def supports_external_lookups(self):
445
if self._supports_external_lookups is None:
447
self._supports_external_lookups = \
448
self._custom_format.supports_external_lookups
449
return self._supports_external_lookups
452
def supports_tree_reference(self):
453
if self._supports_tree_reference is None:
455
self._supports_tree_reference = \
456
self._custom_format.supports_tree_reference
457
return self._supports_tree_reference
459
def _vfs_initialize(self, a_bzrdir, shared):
460
"""Helper for common code in initialize."""
461
if self._custom_format:
462
# Custom format requested
463
result = self._custom_format.initialize(a_bzrdir, shared=shared)
464
elif self._creating_bzrdir is not None:
465
# Use the format that the repository we were created to back
261
def initialize(self, a_bzrdir, shared=False):
262
if not isinstance(a_bzrdir, RemoteBzrDir):
467
263
prior_repo = self._creating_bzrdir.open_repository()
468
264
prior_repo._ensure_real()
469
result = prior_repo._real_repository._format.initialize(
265
return prior_repo._real_repository._format.initialize(
470
266
a_bzrdir, shared=shared)
472
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
473
# support remote initialization.
474
# We delegate to a real object at this point (as RemoteBzrDir
475
# delegate to the repository format which would lead to infinite
476
# recursion if we just called a_bzrdir.create_repository.
477
a_bzrdir._ensure_real()
478
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
479
if not isinstance(result, RemoteRepository):
480
return self.open(a_bzrdir)
484
def initialize(self, a_bzrdir, shared=False):
485
# Being asked to create on a non RemoteBzrDir:
486
if not isinstance(a_bzrdir, RemoteBzrDir):
487
return self._vfs_initialize(a_bzrdir, shared)
488
medium = a_bzrdir._client._medium
489
if medium._is_remote_before((1, 13)):
490
return self._vfs_initialize(a_bzrdir, shared)
491
# Creating on a remote bzr dir.
492
# 1) get the network name to use.
493
if self._custom_format:
494
network_name = self._custom_format.network_name()
495
elif self._network_name:
496
network_name = self._network_name
498
# Select the current bzrlib default and ask for that.
499
reference_bzrdir_format = bzrdir.format_registry.get('default')()
500
reference_format = reference_bzrdir_format.repository_format
501
network_name = reference_format.network_name()
502
# 2) try direct creation via RPC
503
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
504
verb = 'BzrDir.create_repository'
510
response = a_bzrdir._call(verb, path, network_name, shared_str)
511
except errors.UnknownSmartMethod:
512
# Fallback - use vfs methods
513
medium._remember_remote_is_before((1, 13))
514
return self._vfs_initialize(a_bzrdir, shared)
516
# Turn the response into a RemoteRepository object.
517
format = response_tuple_to_repo_format(response[1:])
518
# Used to support creating a real format instance when needed.
519
format._creating_bzrdir = a_bzrdir
520
remote_repo = RemoteRepository(a_bzrdir, format)
521
format._creating_repo = remote_repo
267
return a_bzrdir.create_repository(shared=shared)
524
269
def open(self, a_bzrdir):
525
270
if not isinstance(a_bzrdir, RemoteBzrDir):
526
271
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
527
272
return a_bzrdir.open_repository()
529
def _ensure_real(self):
530
if self._custom_format is None:
531
self._custom_format = repository.network_format_registry.get(
535
def _fetch_order(self):
537
return self._custom_format._fetch_order
540
def _fetch_uses_deltas(self):
542
return self._custom_format._fetch_uses_deltas
545
def _fetch_reconcile(self):
547
return self._custom_format._fetch_reconcile
549
274
def get_format_description(self):
550
275
return 'bzr remote repository'
552
277
def __eq__(self, other):
553
return self.__class__ is other.__class__
278
return self.__class__ == other.__class__
555
280
def check_conversion_target(self, target_format):
556
281
if self.rich_root_data and not target_format.rich_root_data:
743
427
for line in lines:
744
428
d = tuple(line.split())
745
429
revision_graph[d[0]] = d[1:]
747
431
return revision_graph
750
"""See Repository._get_sink()."""
751
return RemoteStreamSink(self)
753
def _get_source(self, to_format):
754
"""Return a source for streaming from this repository."""
755
return RemoteStreamSource(self, to_format)
758
433
def has_revision(self, revision_id):
759
"""True if this repository has a copy of the revision."""
760
# Copy of bzrlib.repository.Repository.has_revision
761
return revision_id in self.has_revisions((revision_id,))
434
"""See Repository.has_revision()."""
435
if revision_id == NULL_REVISION:
436
# The null revision is always present.
438
path = self.bzrdir._path_for_remote_call(self._client)
439
response = self._call('Repository.has_revision', path, revision_id)
440
if response[0] not in ('yes', 'no'):
441
raise errors.UnexpectedSmartServerResponse(response)
442
if response[0] == 'yes':
444
for fallback_repo in self._fallback_repositories:
445
if fallback_repo.has_revision(revision_id):
764
449
def has_revisions(self, revision_ids):
765
"""Probe to find out the presence of multiple revisions.
767
:param revision_ids: An iterable of revision_ids.
768
:return: A set of the revision_ids that were present.
770
# Copy of bzrlib.repository.Repository.has_revisions
771
parent_map = self.get_parent_map(revision_ids)
772
result = set(parent_map)
773
if _mod_revision.NULL_REVISION in revision_ids:
774
result.add(_mod_revision.NULL_REVISION)
450
"""See Repository.has_revisions()."""
451
# FIXME: This does many roundtrips, particularly when there are
452
# fallback repositories. -- mbp 20080905
454
for revision_id in revision_ids:
455
if self.has_revision(revision_id):
456
result.add(revision_id)
777
459
def has_same_location(self, other):
778
return (self.__class__ is other.__class__ and
460
return (self.__class__ == other.__class__ and
779
461
self.bzrdir.transport.base == other.bzrdir.transport.base)
781
463
def get_graph(self, other_repository=None):
782
464
"""Return the graph for this repository format"""
783
parents_provider = self._make_parents_provider(other_repository)
465
parents_provider = self
466
if (other_repository is not None and
467
other_repository.bzrdir.transport.base !=
468
self.bzrdir.transport.base):
469
parents_provider = graph._StackedParentsProvider(
470
[parents_provider, other_repository._make_parents_provider()])
784
471
return graph.Graph(parents_provider)
786
473
def gather_stats(self, revid=None, committers=None):
923
608
implemented operations.
925
610
if self._real_repository is not None:
926
# Replacing an already set real repository.
927
# We cannot do this [currently] if the repository is locked -
928
# synchronised state might be lost.
930
raise AssertionError('_real_repository is already set')
611
raise AssertionError('_real_repository is already set')
931
612
if isinstance(repository, RemoteRepository):
932
613
raise AssertionError()
933
614
self._real_repository = repository
934
# three code paths happen here:
935
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
936
# up stacking. In this case self._fallback_repositories is [], and the
937
# real repo is already setup. Preserve the real repo and
938
# RemoteRepository.add_fallback_repository will avoid adding
940
# 2) new servers, RemoteBranch.open() sets up stacking, and when
941
# ensure_real is triggered from a branch, the real repository to
942
# set already has a matching list with separate instances, but
943
# as they are also RemoteRepositories we don't worry about making the
944
# lists be identical.
945
# 3) new servers, RemoteRepository.ensure_real is triggered before
946
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
947
# and need to populate it.
948
if (self._fallback_repositories and
949
len(self._real_repository._fallback_repositories) !=
950
len(self._fallback_repositories)):
951
if len(self._real_repository._fallback_repositories):
952
raise AssertionError(
953
"cannot cleanly remove existing _fallback_repositories")
954
615
for fb in self._fallback_repositories:
955
616
self._real_repository.add_fallback_repository(fb)
956
617
if self._lock_mode == 'w':
1072
733
def add_fallback_repository(self, repository):
1073
734
"""Add a repository to use for looking up data not held locally.
1075
736
:param repository: A repository.
1077
if not self._format.supports_external_lookups:
1078
raise errors.UnstackableRepositoryFormat(
1079
self._format.network_name(), self.base)
738
# XXX: At the moment the RemoteRepository will allow fallbacks
739
# unconditionally - however, a _real_repository will usually exist,
740
# and may raise an error if it's not accommodated by the underlying
741
# format. Eventually we should check when opening the repository
742
# whether it's willing to allow them or not.
1080
744
# We need to accumulate additional repositories here, to pass them in
1081
745
# on various RPC's.
1083
746
self._fallback_repositories.append(repository)
1084
# If self._real_repository was parameterised already (e.g. because a
1085
# _real_branch had its get_stacked_on_url method called), then the
1086
# repository to be added may already be in the _real_repositories list.
1087
if self._real_repository is not None:
1088
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1089
self._real_repository._fallback_repositories]
1090
if repository.bzrdir.root_transport.base not in fallback_locations:
1091
self._real_repository.add_fallback_repository(repository)
747
# They are also seen by the fallback repository. If it doesn't exist
748
# yet they'll be added then. This implicitly copies them.
1093
751
def add_inventory(self, revid, inv, parents):
1094
752
self._ensure_real()
1095
753
return self._real_repository.add_inventory(revid, inv, parents)
1097
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1100
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1101
delta, new_revision_id, parents)
1103
755
def add_revision(self, rev_id, rev, inv=None, config=None):
1104
756
self._ensure_real()
1105
757
return self._real_repository.add_revision(
1171
809
return repository.InterRepository.get(
1172
810
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1174
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1176
# No base implementation to use as RemoteRepository is not a subclass
1177
# of Repository; so this is a copy of Repository.fetch().
1178
if fetch_spec is not None and revision_id is not None:
1179
raise AssertionError(
1180
"fetch_spec and revision_id are mutually exclusive.")
1181
if self.is_in_write_group():
1182
raise errors.InternalBzrError(
1183
"May not fetch while in a write group.")
1184
# fast path same-url fetch operations
1185
if self.has_same_location(source) and fetch_spec is None:
812
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False):
813
# Not delegated to _real_repository so that InterRepository.get has a
814
# chance to find an InterRepository specialised for RemoteRepository.
815
if self.has_same_location(source):
1186
816
# check that last_revision is in 'from' and then return a
1188
818
if (revision_id is not None and
1189
819
not revision.is_null(revision_id)):
1190
820
self.get_revision(revision_id)
1192
# if there is no specific appropriate InterRepository, this will get
1193
# the InterRepository base class, which raises an
1194
# IncompatibleRepositories when asked to fetch.
1195
822
inter = repository.InterRepository.get(source, self)
1196
return inter.fetch(revision_id=revision_id, pb=pb,
1197
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
824
return inter.fetch(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts)
825
except NotImplementedError:
826
raise errors.IncompatibleRepositories(source, self)
1199
828
def create_bundle(self, target, base, fileobj, format=None):
1200
829
self._ensure_real()
1213
842
self._ensure_real()
1214
843
return self._real_repository._get_versioned_file_checker(
1215
844
revisions, revision_versions_cache)
1217
846
def iter_files_bytes(self, desired_files):
1218
847
"""See Repository.iter_file_bytes.
1220
849
self._ensure_real()
1221
850
return self._real_repository.iter_files_bytes(desired_files)
1223
def get_parent_map(self, revision_ids):
853
def _fetch_order(self):
854
"""Decorate the real repository for now.
856
In the long term getting this back from the remote repository as part
857
of open would be more efficient.
860
return self._real_repository._fetch_order
863
def _fetch_uses_deltas(self):
864
"""Decorate the real repository for now.
866
In the long term getting this back from the remote repository as part
867
of open would be more efficient.
870
return self._real_repository._fetch_uses_deltas
873
def _fetch_reconcile(self):
874
"""Decorate the real repository for now.
876
In the long term getting this back from the remote repository as part
877
of open would be more efficient.
880
return self._real_repository._fetch_reconcile
882
def get_parent_map(self, keys):
1224
883
"""See bzrlib.Graph.get_parent_map()."""
1225
return self._make_parents_provider().get_parent_map(revision_ids)
884
# Hack to build up the caching logic.
885
ancestry = self._parents_map
887
# Repository is not locked, so there's no cache.
888
missing_revisions = set(keys)
891
missing_revisions = set(key for key in keys if key not in ancestry)
892
if missing_revisions:
893
parent_map = self._get_parent_map(missing_revisions)
894
if 'hpss' in debug.debug_flags:
895
mutter('retransmitted revisions: %d of %d',
896
len(set(ancestry).intersection(parent_map)),
898
ancestry.update(parent_map)
899
present_keys = [k for k in keys if k in ancestry]
900
if 'hpss' in debug.debug_flags:
901
if self._requested_parents is not None and len(ancestry) != 0:
902
self._requested_parents.update(present_keys)
903
mutter('Current RemoteRepository graph hit rate: %d%%',
904
100.0 * len(self._requested_parents) / len(ancestry))
905
return dict((k, ancestry[k]) for k in present_keys)
1227
def _get_parent_map_rpc(self, keys):
907
def _get_parent_map(self, keys):
1228
908
"""Helper for get_parent_map that performs the RPC."""
1229
909
medium = self._client._medium
1230
910
if medium._is_remote_before((1, 2)):
1231
911
# We already found out that the server can't understand
1232
912
# Repository.get_parent_map requests, so just fetch the whole
1235
# Note that this reads the whole graph, when only some keys are
1236
# wanted. On this old server there's no way (?) to get them all
1237
# in one go, and the user probably will have seen a warning about
1238
# the server being old anyhow.
1239
rg = self._get_revision_graph(None)
1240
# There is an API discrepancy between get_parent_map and
914
# XXX: Note that this will issue a deprecation warning. This is ok
915
# :- its because we're working with a deprecated server anyway, and
916
# the user will almost certainly have seen a warning about the
917
# server version already.
918
rg = self.get_revision_graph()
919
# There is an api discrepency between get_parent_map and
1241
920
# get_revision_graph. Specifically, a "key:()" pair in
1242
921
# get_revision_graph just means a node has no parents. For
1243
922
# "get_parent_map" it means the node is a ghost. So fix up the
1273
952
# TODO: Manage this incrementally to avoid covering the same path
1274
953
# repeatedly. (The server will have to on each request, but the less
1275
954
# work done the better).
1277
# Negative caching notes:
1278
# new server sends missing when a request including the revid
1279
# 'include-missing:' is present in the request.
1280
# missing keys are serialised as missing:X, and we then call
1281
# provider.note_missing(X) for-all X
1282
parents_map = self._unstacked_provider.get_cached_map()
955
parents_map = self._parents_map
1283
956
if parents_map is None:
1284
957
# Repository is not locked, so there's no cache.
1285
958
parents_map = {}
1286
# start_set is all the keys in the cache
1287
959
start_set = set(parents_map)
1288
# result set is all the references to keys in the cache
1289
960
result_parents = set()
1290
961
for parents in parents_map.itervalues():
1291
962
result_parents.update(parents)
1292
963
stop_keys = result_parents.difference(start_set)
1293
# We don't need to send ghosts back to the server as a position to
1295
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1296
key_count = len(parents_map)
1297
if (NULL_REVISION in result_parents
1298
and NULL_REVISION in self._unstacked_provider.missing_keys):
1299
# If we pruned NULL_REVISION from the stop_keys because it's also
1300
# in our cache of "missing" keys we need to increment our key count
1301
# by 1, because the reconsitituted SearchResult on the server will
1302
# still consider NULL_REVISION to be an included key.
1304
964
included_keys = start_set.intersection(result_parents)
1305
965
start_set.difference_update(included_keys)
1306
recipe = ('manual', start_set, stop_keys, key_count)
966
recipe = (start_set, stop_keys, len(parents_map))
1307
967
body = self._serialise_search_recipe(recipe)
1308
968
path = self.bzrdir._path_for_remote_call(self._client)
1309
969
for key in keys:
1592
1226
self._ensure_real()
1593
1227
self._real_repository._pack_collection.autopack()
1229
if self._real_repository is not None:
1230
# Reset the real repository's cache of pack names.
1231
# XXX: At some point we may be able to skip this and just rely on
1232
# the automatic retry logic to do the right thing, but for now we
1233
# err on the side of being correct rather than being optimal.
1234
self._real_repository._pack_collection.reload_pack_names()
1596
1235
if response[0] != 'ok':
1597
1236
raise errors.UnexpectedSmartServerResponse(response)
1600
class RemoteStreamSink(repository.StreamSink):
1602
def _insert_real(self, stream, src_format, resume_tokens):
1603
self.target_repo._ensure_real()
1604
sink = self.target_repo._real_repository._get_sink()
1605
result = sink.insert_stream(stream, src_format, resume_tokens)
1607
self.target_repo.autopack()
1610
def insert_stream(self, stream, src_format, resume_tokens):
1611
target = self.target_repo
1612
target._unstacked_provider.missing_keys.clear()
1613
if target._lock_token:
1614
verb = 'Repository.insert_stream_locked'
1615
extra_args = (target._lock_token or '',)
1616
required_version = (1, 14)
1618
verb = 'Repository.insert_stream'
1620
required_version = (1, 13)
1621
client = target._client
1622
medium = client._medium
1623
if medium._is_remote_before(required_version):
1624
# No possible way this can work.
1625
return self._insert_real(stream, src_format, resume_tokens)
1626
path = target.bzrdir._path_for_remote_call(client)
1627
if not resume_tokens:
1628
# XXX: Ugly but important for correctness, *will* be fixed during
1629
# 1.13 cycle. Pushing a stream that is interrupted results in a
1630
# fallback to the _real_repositories sink *with a partial stream*.
1631
# Thats bad because we insert less data than bzr expected. To avoid
1632
# this we do a trial push to make sure the verb is accessible, and
1633
# do not fallback when actually pushing the stream. A cleanup patch
1634
# is going to look at rewinding/restarting the stream/partial
1636
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1638
response = client.call_with_body_stream(
1639
(verb, path, '') + extra_args, byte_stream)
1640
except errors.UnknownSmartMethod:
1641
medium._remember_remote_is_before(required_version)
1642
return self._insert_real(stream, src_format, resume_tokens)
1643
byte_stream = smart_repo._stream_to_byte_stream(
1645
resume_tokens = ' '.join(resume_tokens)
1646
response = client.call_with_body_stream(
1647
(verb, path, resume_tokens) + extra_args, byte_stream)
1648
if response[0][0] not in ('ok', 'missing-basis'):
1649
raise errors.UnexpectedSmartServerResponse(response)
1650
if response[0][0] == 'missing-basis':
1651
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1652
resume_tokens = tokens
1653
return resume_tokens, set(missing_keys)
1655
self.target_repo.refresh_data()
1659
class RemoteStreamSource(repository.StreamSource):
1660
"""Stream data from a remote server."""
1662
def get_stream(self, search):
1663
if (self.from_repository._fallback_repositories and
1664
self.to_format._fetch_order == 'topological'):
1665
return self._real_stream(self.from_repository, search)
1666
return self.missing_parents_chain(search, [self.from_repository] +
1667
self.from_repository._fallback_repositories)
1669
def _real_stream(self, repo, search):
1670
"""Get a stream for search from repo.
1672
This never called RemoteStreamSource.get_stream, and is a heler
1673
for RemoteStreamSource._get_stream to allow getting a stream
1674
reliably whether fallback back because of old servers or trying
1675
to stream from a non-RemoteRepository (which the stacked support
1678
source = repo._get_source(self.to_format)
1679
if isinstance(source, RemoteStreamSource):
1680
return repository.StreamSource.get_stream(source, search)
1681
return source.get_stream(search)
1683
def _get_stream(self, repo, search):
1684
"""Core worker to get a stream from repo for search.
1686
This is used by both get_stream and the stacking support logic. It
1687
deliberately gets a stream for repo which does not need to be
1688
self.from_repository. In the event that repo is not Remote, or
1689
cannot do a smart stream, a fallback is made to the generic
1690
repository._get_stream() interface, via self._real_stream.
1692
In the event of stacking, streams from _get_stream will not
1693
contain all the data for search - this is normal (see get_stream).
1695
:param repo: A repository.
1696
:param search: A search.
1698
# Fallbacks may be non-smart
1699
if not isinstance(repo, RemoteRepository):
1700
return self._real_stream(repo, search)
1701
client = repo._client
1702
medium = client._medium
1703
if medium._is_remote_before((1, 13)):
1704
# streaming was added in 1.13
1705
return self._real_stream(repo, search)
1706
path = repo.bzrdir._path_for_remote_call(client)
1708
search_bytes = repo._serialise_search_result(search)
1709
response = repo._call_with_body_bytes_expecting_body(
1710
'Repository.get_stream',
1711
(path, self.to_format.network_name()), search_bytes)
1712
response_tuple, response_handler = response
1713
except errors.UnknownSmartMethod:
1714
medium._remember_remote_is_before((1,13))
1715
return self._real_stream(repo, search)
1716
if response_tuple[0] != 'ok':
1717
raise errors.UnexpectedSmartServerResponse(response_tuple)
1718
byte_stream = response_handler.read_streamed_body()
1719
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1720
if src_format.network_name() != repo._format.network_name():
1721
raise AssertionError(
1722
"Mismatched RemoteRepository and stream src %r, %r" % (
1723
src_format.network_name(), repo._format.network_name()))
1726
def missing_parents_chain(self, search, sources):
1727
"""Chain multiple streams together to handle stacking.
1729
:param search: The overall search to satisfy with streams.
1730
:param sources: A list of Repository objects to query.
1732
self.serialiser = self.to_format._serializer
1733
self.seen_revs = set()
1734
self.referenced_revs = set()
1735
# If there are heads in the search, or the key count is > 0, we are not
1737
while not search.is_empty() and len(sources) > 1:
1738
source = sources.pop(0)
1739
stream = self._get_stream(source, search)
1740
for kind, substream in stream:
1741
if kind != 'revisions':
1742
yield kind, substream
1744
yield kind, self.missing_parents_rev_handler(substream)
1745
search = search.refine(self.seen_revs, self.referenced_revs)
1746
self.seen_revs = set()
1747
self.referenced_revs = set()
1748
if not search.is_empty():
1749
for kind, stream in self._get_stream(sources[0], search):
1752
def missing_parents_rev_handler(self, substream):
1753
for content in substream:
1754
revision_bytes = content.get_bytes_as('fulltext')
1755
revision = self.serialiser.read_revision_from_string(revision_bytes)
1756
self.seen_revs.add(content.key[-1])
1757
self.referenced_revs.update(revision.parent_ids)
1761
1239
class RemoteBranchLockableFiles(LockableFiles):
1762
1240
"""A 'LockableFiles' implementation that talks to a smart server.
1764
1242
This is not a public interface class.
1781
1259
class RemoteBranchFormat(branch.BranchFormat):
1783
def __init__(self, network_name=None):
1784
super(RemoteBranchFormat, self).__init__()
1785
self._matchingbzrdir = RemoteBzrDirFormat()
1786
self._matchingbzrdir.set_branch_format(self)
1787
self._custom_format = None
1788
self._network_name = network_name
1790
1261
def __eq__(self, other):
1791
return (isinstance(other, RemoteBranchFormat) and
1262
return (isinstance(other, RemoteBranchFormat) and
1792
1263
self.__dict__ == other.__dict__)
1794
def _ensure_real(self):
1795
if self._custom_format is None:
1796
self._custom_format = branch.network_format_registry.get(
1799
1265
def get_format_description(self):
1800
1266
return 'Remote BZR Branch'
1802
def network_name(self):
1803
return self._network_name
1805
def open(self, a_bzrdir, ignore_fallbacks=False):
1806
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1808
def _vfs_initialize(self, a_bzrdir):
1809
# Initialisation when using a local bzrdir object, or a non-vfs init
1810
# method is not available on the server.
1811
# self._custom_format is always set - the start of initialize ensures
1813
if isinstance(a_bzrdir, RemoteBzrDir):
1814
a_bzrdir._ensure_real()
1815
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1817
# We assume the bzrdir is parameterised; it may not be.
1818
result = self._custom_format.initialize(a_bzrdir)
1819
if (isinstance(a_bzrdir, RemoteBzrDir) and
1820
not isinstance(result, RemoteBranch)):
1821
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1268
def get_format_string(self):
1269
return 'Remote BZR Branch'
1271
def open(self, a_bzrdir):
1272
return a_bzrdir.open_branch()
1824
1274
def initialize(self, a_bzrdir):
1825
# 1) get the network name to use.
1826
if self._custom_format:
1827
network_name = self._custom_format.network_name()
1829
# Select the current bzrlib default and ask for that.
1830
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1831
reference_format = reference_bzrdir_format.get_branch_format()
1832
self._custom_format = reference_format
1833
network_name = reference_format.network_name()
1834
# Being asked to create on a non RemoteBzrDir:
1835
if not isinstance(a_bzrdir, RemoteBzrDir):
1836
return self._vfs_initialize(a_bzrdir)
1837
medium = a_bzrdir._client._medium
1838
if medium._is_remote_before((1, 13)):
1839
return self._vfs_initialize(a_bzrdir)
1840
# Creating on a remote bzr dir.
1841
# 2) try direct creation via RPC
1842
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1843
verb = 'BzrDir.create_branch'
1845
response = a_bzrdir._call(verb, path, network_name)
1846
except errors.UnknownSmartMethod:
1847
# Fallback - use vfs methods
1848
medium._remember_remote_is_before((1, 13))
1849
return self._vfs_initialize(a_bzrdir)
1850
if response[0] != 'ok':
1851
raise errors.UnexpectedSmartServerResponse(response)
1852
# Turn the response into a RemoteRepository object.
1853
format = RemoteBranchFormat(network_name=response[1])
1854
repo_format = response_tuple_to_repo_format(response[3:])
1855
if response[2] == '':
1856
repo_bzrdir = a_bzrdir
1858
repo_bzrdir = RemoteBzrDir(
1859
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1861
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1862
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1863
format=format, setup_stacking=False)
1864
# XXX: We know this is a new branch, so it must have revno 0, revid
1865
# NULL_REVISION. Creating the branch locked would make this be unable
1866
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1867
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1868
return remote_branch
1870
def make_tags(self, branch):
1872
return self._custom_format.make_tags(branch)
1275
return a_bzrdir.create_branch()
1874
1277
def supports_tags(self):
1875
1278
# Remote branches might support tags, but we won't know until we
1876
1279
# access the real remote branch.
1878
return self._custom_format.supports_tags()
1880
def supports_stacking(self):
1882
return self._custom_format.supports_stacking()
1885
1283
class RemoteBranch(branch.Branch, _RpcHelper):
2249
1599
raise errors.UnexpectedSmartServerResponse(response)
2250
1600
new_revno, new_revision_id = response[1:]
2251
1601
self._last_revision_info_cache = new_revno, new_revision_id
2252
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2253
1602
if self._real_branch is not None:
2254
1603
cache = new_revno, new_revision_id
2255
1604
self._real_branch._last_revision_info_cache = cache
2257
1606
def _set_last_revision(self, revision_id):
2258
old_revno, old_revid = self.last_revision_info()
2259
# This performs additional work to meet the hook contract; while its
2260
# undesirable, we have to synthesise the revno to call the hook, and
2261
# not calling the hook is worse as it means changes can't be prevented.
2262
# Having calculated this though, we can't just call into
2263
# set_last_revision_info as a simple call, because there is a set_rh
2264
# hook that some folk may still be using.
2265
history = self._lefthand_history(revision_id)
2266
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2267
1607
self._clear_cached_state()
2268
1608
response = self._call('Branch.set_last_revision',
2269
1609
self._remote_path(), self._lock_token, self._repo_lock_token,
2271
1611
if response != ('ok',):
2272
1612
raise errors.UnexpectedSmartServerResponse(response)
2273
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2275
1614
@needs_write_lock
2276
1615
def set_revision_history(self, rev_history):
2283
1622
rev_id = rev_history[-1]
2284
1623
self._set_last_revision(rev_id)
2285
for hook in branch.Branch.hooks['set_rh']:
2286
hook(self, rev_history)
2287
1624
self._cache_revision_history(rev_history)
2289
def _get_parent_location(self):
2290
medium = self._client._medium
2291
if medium._is_remote_before((1, 13)):
2292
return self._vfs_get_parent_location()
2294
response = self._call('Branch.get_parent', self._remote_path())
2295
except errors.UnknownSmartMethod:
2296
medium._remember_remote_is_before((1, 13))
2297
return self._vfs_get_parent_location()
2298
if len(response) != 1:
2299
raise errors.UnexpectedSmartServerResponse(response)
2300
parent_location = response[0]
2301
if parent_location == '':
2303
return parent_location
2305
def _vfs_get_parent_location(self):
2307
return self._real_branch._get_parent_location()
2309
def _set_parent_location(self, url):
2310
medium = self._client._medium
2311
if medium._is_remote_before((1, 15)):
2312
return self._vfs_set_parent_location(url)
2314
call_url = url or ''
2315
if type(call_url) is not str:
2316
raise AssertionError('url must be a str or None (%s)' % url)
2317
response = self._call('Branch.set_parent_location',
2318
self._remote_path(), self._lock_token, self._repo_lock_token,
2320
except errors.UnknownSmartMethod:
2321
medium._remember_remote_is_before((1, 15))
2322
return self._vfs_set_parent_location(url)
2324
raise errors.UnexpectedSmartServerResponse(response)
2326
def _vfs_set_parent_location(self, url):
2328
return self._real_branch._set_parent_location(url)
1626
def get_parent(self):
1628
return self._real_branch.get_parent()
1630
def set_parent(self, url):
1632
return self._real_branch.set_parent(url)
1634
def set_stacked_on_url(self, stacked_location):
1635
"""Set the URL this branch is stacked against.
1637
:raises UnstackableBranchFormat: If the branch does not support
1639
:raises UnstackableRepositoryFormat: If the repository does not support
1643
return self._real_branch.set_stacked_on_url(stacked_location)
1645
def sprout(self, to_bzrdir, revision_id=None):
1646
branch_format = to_bzrdir._format._branch_format
1647
if (branch_format is None or
1648
isinstance(branch_format, RemoteBranchFormat)):
1649
# The to_bzrdir specifies RemoteBranchFormat (or no format, which
1650
# implies the same thing), but RemoteBranches can't be created at
1651
# arbitrary URLs. So create a branch in the same format as
1652
# _real_branch instead.
1653
# XXX: if to_bzrdir is a RemoteBzrDir, this should perhaps do
1654
# to_bzrdir.create_branch to create a RemoteBranch after all...
1656
result = self._real_branch._format.initialize(to_bzrdir)
1657
self.copy_content_into(result, revision_id=revision_id)
1658
result.set_parent(self.bzrdir.root_transport.base)
1660
result = branch.Branch.sprout(
1661
self, to_bzrdir, revision_id=revision_id)
2330
1664
@needs_write_lock
2331
1665
def pull(self, source, overwrite=False, stop_revision=None,
2391
1720
except errors.UnknownSmartMethod:
2392
1721
medium._remember_remote_is_before((1, 6))
2393
1722
self._clear_cached_state_of_remote_branch_only()
2394
self.set_revision_history(self._lefthand_history(revision_id,
2395
last_rev=last_rev,other_branch=other_branch))
1724
self._real_branch.generate_revision_history(
1725
revision_id, last_rev=last_rev, other_branch=other_branch)
1730
return self._real_branch.tags
2397
1732
def set_push_location(self, location):
2398
1733
self._ensure_real()
2399
1734
return self._real_branch.set_push_location(location)
2402
class RemoteConfig(object):
2403
"""A Config that reads and writes from smart verbs.
2405
It is a low-level object that considers config data to be name/value pairs
2406
that may be associated with a section. Assigning meaning to the these
2407
values is done at higher levels like bzrlib.config.TreeConfig.
2410
def get_option(self, name, section=None, default=None):
2411
"""Return the value associated with a named option.
2413
:param name: The name of the value
2414
:param section: The section the option is in (if any)
2415
:param default: The value to return if the value is not set
2416
:return: The value or default value
1737
def update_revisions(self, other, stop_revision=None, overwrite=False,
1739
"""See Branch.update_revisions."""
2419
configobj = self._get_configobj()
2421
section_obj = configobj
1742
if stop_revision is None:
1743
stop_revision = other.last_revision()
1744
if revision.is_null(stop_revision):
1745
# if there are no commits, we're done.
1747
self.fetch(other, stop_revision)
1750
# Just unconditionally set the new revision. We don't care if
1751
# the branches have diverged.
1752
self._set_last_revision(stop_revision)
2424
section_obj = configobj[section]
2427
return section_obj.get(name, default)
2428
except errors.UnknownSmartMethod:
2429
return self._vfs_get_option(name, section, default)
2431
def _response_to_configobj(self, response):
2432
if len(response[0]) and response[0][0] != 'ok':
2433
raise errors.UnexpectedSmartServerResponse(response)
2434
lines = response[1].read_body_bytes().splitlines()
2435
return config.ConfigObj(lines, encoding='utf-8')
2438
class RemoteBranchConfig(RemoteConfig):
2439
"""A RemoteConfig for Branches."""
2441
def __init__(self, branch):
2442
self._branch = branch
2444
def _get_configobj(self):
2445
path = self._branch._remote_path()
2446
response = self._branch._client.call_expecting_body(
2447
'Branch.get_config_file', path)
2448
return self._response_to_configobj(response)
2450
def set_option(self, value, name, section=None):
2451
"""Set the value associated with a named option.
2453
:param value: The value to set
2454
:param name: The name of the value to set
2455
:param section: The section the option is in (if any)
2457
medium = self._branch._client._medium
2458
if medium._is_remote_before((1, 14)):
2459
return self._vfs_set_option(value, name, section)
2461
path = self._branch._remote_path()
2462
response = self._branch._client.call('Branch.set_config_option',
2463
path, self._branch._lock_token, self._branch._repo_lock_token,
2464
value.encode('utf8'), name, section or '')
2465
except errors.UnknownSmartMethod:
2466
medium._remember_remote_is_before((1, 14))
2467
return self._vfs_set_option(value, name, section)
2469
raise errors.UnexpectedSmartServerResponse(response)
2471
def _real_object(self):
2472
self._branch._ensure_real()
2473
return self._branch._real_branch
2475
def _vfs_set_option(self, value, name, section=None):
2476
return self._real_object()._get_config().set_option(
2477
value, name, section)
2480
class RemoteBzrDirConfig(RemoteConfig):
2481
"""A RemoteConfig for BzrDirs."""
2483
def __init__(self, bzrdir):
2484
self._bzrdir = bzrdir
2486
def _get_configobj(self):
2487
medium = self._bzrdir._client._medium
2488
verb = 'BzrDir.get_config_file'
2489
if medium._is_remote_before((1, 15)):
2490
raise errors.UnknownSmartMethod(verb)
2491
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2492
response = self._bzrdir._call_expecting_body(
2494
return self._response_to_configobj(response)
2496
def _vfs_get_option(self, name, section, default):
2497
return self._real_object()._get_config().get_option(
2498
name, section, default)
2500
def set_option(self, value, name, section=None):
2501
"""Set the value associated with a named option.
2503
:param value: The value to set
2504
:param name: The name of the value to set
2505
:param section: The section the option is in (if any)
2507
return self._real_object()._get_config().set_option(
2508
value, name, section)
2510
def _real_object(self):
2511
self._bzrdir._ensure_real()
2512
return self._bzrdir._real_bzrdir
1754
medium = self._client._medium
1755
if not medium._is_remote_before((1, 6)):
1757
self._set_last_revision_descendant(stop_revision, other)
1759
except errors.UnknownSmartMethod:
1760
medium._remember_remote_is_before((1, 6))
1761
# Fallback for pre-1.6 servers: check for divergence
1762
# client-side, then do _set_last_revision.
1763
last_rev = revision.ensure_null(self.last_revision())
1765
graph = self.repository.get_graph()
1766
if self._check_if_descendant_or_diverged(
1767
stop_revision, last_rev, graph, other):
1768
# stop_revision is a descendant of last_rev, but we aren't
1769
# overwriting, so we're done.
1771
self._set_last_revision(stop_revision)
2516
1776
def _extract_tar(tar, to_dir):