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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
19
from bzrlib import (
31
revision as _mod_revision,
33
35
from bzrlib.branch import BranchReferenceFormat
34
36
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
36
38
from bzrlib.errors import (
38
40
SmartProtocolError,
40
42
from bzrlib.lockable_files import LockableFiles
41
from bzrlib.smart import client, vfs
43
from bzrlib.smart import client, vfs, repository as smart_repo
42
44
from bzrlib.revision import ensure_null, NULL_REVISION
43
45
from bzrlib.trace import mutter, note, warning
48
class _RpcHelper(object):
49
"""Mixin class that helps with issuing RPCs."""
51
def _call(self, method, *args, **err_context):
53
return self._client.call(method, *args)
54
except errors.ErrorFromSmartServer, err:
55
self._translate_error(err, **err_context)
57
def _call_expecting_body(self, method, *args, **err_context):
59
return self._client.call_expecting_body(method, *args)
60
except errors.ErrorFromSmartServer, err:
61
self._translate_error(err, **err_context)
63
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
65
return self._client.call_with_body_bytes(method, args, body_bytes)
66
except errors.ErrorFromSmartServer, err:
67
self._translate_error(err, **err_context)
69
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
72
return self._client.call_with_body_bytes_expecting_body(
73
method, args, body_bytes)
74
except errors.ErrorFromSmartServer, err:
75
self._translate_error(err, **err_context)
78
def response_tuple_to_repo_format(response):
79
"""Convert a response tuple describing a repository format to a format."""
80
format = RemoteRepositoryFormat()
81
format._rich_root_data = (response[0] == 'yes')
82
format._supports_tree_reference = (response[1] == 'yes')
83
format._supports_external_lookups = (response[2] == 'yes')
84
format._network_name = response[3]
46
88
# Note: RemoteBzrDirFormat is in bzrdir.py
48
class RemoteBzrDir(BzrDir):
90
class RemoteBzrDir(BzrDir, _RpcHelper):
49
91
"""Control directory on a remote server, accessed via bzr:// or similar."""
51
def __init__(self, transport, _client=None):
93
def __init__(self, transport, format, _client=None, _force_probe=False):
52
94
"""Construct a RemoteBzrDir.
54
96
:param _client: Private parameter for testing. Disables probing and the
55
97
use of a real bzrdir.
57
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
99
BzrDir.__init__(self, transport, format)
58
100
# this object holds a delegated bzrdir that uses file-level operations
59
101
# to talk to the other side
60
102
self._real_bzrdir = None
103
self._has_working_tree = None
104
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
105
# create_branch for details.
106
self._next_open_branch_result = None
62
108
if _client is None:
63
109
medium = transport.get_smart_medium()
64
110
self._client = client._SmartClient(medium)
66
112
self._client = _client
119
return '%s(%r)' % (self.__class__.__name__, self._client)
121
def _probe_bzrdir(self):
122
medium = self._client._medium
69
123
path = self._path_for_remote_call(self._client)
70
response = self._client.call('BzrDir.open', path)
124
if medium._is_remote_before((2, 1)):
128
self._rpc_open_2_1(path)
130
except errors.UnknownSmartMethod:
131
medium._remember_remote_is_before((2, 1))
134
def _rpc_open_2_1(self, path):
135
response = self._call('BzrDir.open_2.1', path)
136
if response == ('no',):
137
raise errors.NotBranchError(path=self.root_transport.base)
138
elif response[0] == 'yes':
139
if response[1] == 'yes':
140
self._has_working_tree = True
141
elif response[1] == 'no':
142
self._has_working_tree = False
144
raise errors.UnexpectedSmartServerResponse(response)
146
raise errors.UnexpectedSmartServerResponse(response)
148
def _rpc_open(self, path):
149
response = self._call('BzrDir.open', path)
71
150
if response not in [('yes',), ('no',)]:
72
151
raise errors.UnexpectedSmartServerResponse(response)
73
152
if response == ('no',):
74
raise errors.NotBranchError(path=transport.base)
153
raise errors.NotBranchError(path=self.root_transport.base)
76
155
def _ensure_real(self):
77
156
"""Ensure that there is a _real_bzrdir set.
79
158
Used before calls to self._real_bzrdir.
81
160
if not self._real_bzrdir:
161
if 'hpssvfs' in debug.debug_flags:
163
warning('VFS BzrDir access triggered\n%s',
164
''.join(traceback.format_stack()))
82
165
self._real_bzrdir = BzrDir.open_from_transport(
83
166
self.root_transport, _server_formats=False)
85
def cloning_metadir(self, stacked=False):
87
return self._real_bzrdir.cloning_metadir(stacked)
167
self._format._network_name = \
168
self._real_bzrdir._format.network_name()
89
170
def _translate_error(self, err, **context):
90
171
_translate_error(err, bzrdir=self, **context)
173
def break_lock(self):
174
# Prevent aliasing problems in the next_open_branch_result cache.
175
# See create_branch for rationale.
176
self._next_open_branch_result = None
177
return BzrDir.break_lock(self)
179
def _vfs_cloning_metadir(self, require_stacking=False):
181
return self._real_bzrdir.cloning_metadir(
182
require_stacking=require_stacking)
184
def cloning_metadir(self, require_stacking=False):
185
medium = self._client._medium
186
if medium._is_remote_before((1, 13)):
187
return self._vfs_cloning_metadir(require_stacking=require_stacking)
188
verb = 'BzrDir.cloning_metadir'
193
path = self._path_for_remote_call(self._client)
195
response = self._call(verb, path, stacking)
196
except errors.UnknownSmartMethod:
197
medium._remember_remote_is_before((1, 13))
198
return self._vfs_cloning_metadir(require_stacking=require_stacking)
199
except errors.UnknownErrorFromSmartServer, err:
200
if err.error_tuple != ('BranchReference',):
202
# We need to resolve the branch reference to determine the
203
# cloning_metadir. This causes unnecessary RPCs to open the
204
# referenced branch (and bzrdir, etc) but only when the caller
205
# didn't already resolve the branch reference.
206
referenced_branch = self.open_branch()
207
return referenced_branch.bzrdir.cloning_metadir()
208
if len(response) != 3:
209
raise errors.UnexpectedSmartServerResponse(response)
210
control_name, repo_name, branch_info = response
211
if len(branch_info) != 2:
212
raise errors.UnexpectedSmartServerResponse(response)
213
branch_ref, branch_name = branch_info
214
format = bzrdir.network_format_registry.get(control_name)
216
format.repository_format = repository.network_format_registry.get(
218
if branch_ref == 'ref':
219
# XXX: we need possible_transports here to avoid reopening the
220
# connection to the referenced location
221
ref_bzrdir = BzrDir.open(branch_name)
222
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
223
format.set_branch_format(branch_format)
224
elif branch_ref == 'branch':
226
format.set_branch_format(
227
branch.network_format_registry.get(branch_name))
229
raise errors.UnexpectedSmartServerResponse(response)
92
232
def create_repository(self, shared=False):
94
self._real_bzrdir.create_repository(shared=shared)
95
return self.open_repository()
233
# as per meta1 formats - just delegate to the format object which may
235
result = self._format.repository_format.initialize(self, shared)
236
if not isinstance(result, RemoteRepository):
237
return self.open_repository()
97
241
def destroy_repository(self):
98
242
"""See BzrDir.destroy_repository"""
99
243
self._ensure_real()
100
244
self._real_bzrdir.destroy_repository()
102
def create_branch(self):
104
real_branch = self._real_bzrdir.create_branch()
105
return RemoteBranch(self, self.find_repository(), real_branch)
246
def create_branch(self, name=None):
247
# as per meta1 formats - just delegate to the format object which may
249
real_branch = self._format.get_branch_format().initialize(self,
251
if not isinstance(real_branch, RemoteBranch):
252
result = RemoteBranch(self, self.find_repository(), real_branch,
256
# BzrDir.clone_on_transport() uses the result of create_branch but does
257
# not return it to its callers; we save approximately 8% of our round
258
# trips by handing the branch we created back to the first caller to
259
# open_branch rather than probing anew. Long term we need a API in
260
# bzrdir that doesn't discard result objects (like result_branch).
262
self._next_open_branch_result = result
107
def destroy_branch(self):
265
def destroy_branch(self, name=None):
108
266
"""See BzrDir.destroy_branch"""
109
267
self._ensure_real()
110
self._real_bzrdir.destroy_branch()
268
self._real_bzrdir.destroy_branch(name=name)
269
self._next_open_branch_result = None
112
271
def create_workingtree(self, revision_id=None, from_branch=None):
113
272
raise errors.NotLocalUrl(self.transport.base)
123
282
def get_branch_reference(self):
124
283
"""See BzrDir.get_branch_reference()."""
284
response = self._get_branch_reference()
285
if response[0] == 'ref':
290
def _get_branch_reference(self):
125
291
path = self._path_for_remote_call(self._client)
127
response = self._client.call('BzrDir.open_branch', path)
128
except errors.ErrorFromSmartServer, err:
129
self._translate_error(err)
130
if response[0] == 'ok':
131
if response[1] == '':
132
# branch at this location.
135
# a branch reference, use the existing BranchReference logic.
292
medium = self._client._medium
294
('BzrDir.open_branchV3', (2, 1)),
295
('BzrDir.open_branchV2', (1, 13)),
296
('BzrDir.open_branch', None),
298
for verb, required_version in candidate_calls:
299
if required_version and medium._is_remote_before(required_version):
302
response = self._call(verb, path)
303
except errors.UnknownSmartMethod:
304
if required_version is None:
306
medium._remember_remote_is_before(required_version)
309
if verb == 'BzrDir.open_branch':
310
if response[0] != 'ok':
311
raise errors.UnexpectedSmartServerResponse(response)
312
if response[1] != '':
313
return ('ref', response[1])
315
return ('branch', '')
316
if response[0] not in ('ref', 'branch'):
138
317
raise errors.UnexpectedSmartServerResponse(response)
140
320
def _get_tree_branch(self):
141
321
"""See BzrDir._get_tree_branch()."""
142
322
return None, self.open_branch()
144
def open_branch(self, _unsupported=False):
324
def open_branch(self, name=None, unsupported=False,
325
ignore_fallbacks=False):
146
327
raise NotImplementedError('unsupported flag support not implemented yet.')
147
reference_url = self.get_branch_reference()
148
if reference_url is None:
149
# branch at this location.
150
return RemoteBranch(self, self.find_repository())
328
if self._next_open_branch_result is not None:
329
# See create_branch for details.
330
result = self._next_open_branch_result
331
self._next_open_branch_result = None
333
response = self._get_branch_reference()
334
if response[0] == 'ref':
152
335
# a branch reference, use the existing BranchReference logic.
153
336
format = BranchReferenceFormat()
154
return format.open(self, _found=True, location=reference_url)
337
return format.open(self, name=name, _found=True,
338
location=response[1], ignore_fallbacks=ignore_fallbacks)
339
branch_format_name = response[1]
340
if not branch_format_name:
341
branch_format_name = None
342
format = RemoteBranchFormat(network_name=branch_format_name)
343
return RemoteBranch(self, self.find_repository(), format=format,
344
setup_stacking=not ignore_fallbacks, name=name)
346
def _open_repo_v1(self, path):
347
verb = 'BzrDir.find_repository'
348
response = self._call(verb, path)
349
if response[0] != 'ok':
350
raise errors.UnexpectedSmartServerResponse(response)
351
# servers that only support the v1 method don't support external
354
repo = self._real_bzrdir.open_repository()
355
response = response + ('no', repo._format.network_name())
356
return response, repo
358
def _open_repo_v2(self, path):
359
verb = 'BzrDir.find_repositoryV2'
360
response = self._call(verb, path)
361
if response[0] != 'ok':
362
raise errors.UnexpectedSmartServerResponse(response)
364
repo = self._real_bzrdir.open_repository()
365
response = response + (repo._format.network_name(),)
366
return response, repo
368
def _open_repo_v3(self, path):
369
verb = 'BzrDir.find_repositoryV3'
370
medium = self._client._medium
371
if medium._is_remote_before((1, 13)):
372
raise errors.UnknownSmartMethod(verb)
374
response = self._call(verb, path)
375
except errors.UnknownSmartMethod:
376
medium._remember_remote_is_before((1, 13))
378
if response[0] != 'ok':
379
raise errors.UnexpectedSmartServerResponse(response)
380
return response, None
156
382
def open_repository(self):
157
383
path = self._path_for_remote_call(self._client)
158
verb = 'BzrDir.find_repositoryV2'
385
for probe in [self._open_repo_v3, self._open_repo_v2,
161
response = self._client.call(verb, path)
388
response, real_repo = probe(path)
162
390
except errors.UnknownSmartMethod:
163
verb = 'BzrDir.find_repository'
164
response = self._client.call(verb, path)
165
except errors.ErrorFromSmartServer, err:
166
self._translate_error(err)
393
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
167
394
if response[0] != 'ok':
168
395
raise errors.UnexpectedSmartServerResponse(response)
169
if verb == 'BzrDir.find_repository':
170
# servers that don't support the V2 method don't support external
172
response = response + ('no', )
173
if not (len(response) == 5):
396
if len(response) != 6:
174
397
raise SmartProtocolError('incorrect response length %s' % (response,))
175
398
if response[1] == '':
176
format = RemoteRepositoryFormat()
177
format.rich_root_data = (response[2] == 'yes')
178
format.supports_tree_reference = (response[3] == 'yes')
179
# No wire format to check this yet.
180
format.supports_external_lookups = (response[4] == 'yes')
399
# repo is at this dir.
400
format = response_tuple_to_repo_format(response[2:])
181
401
# Used to support creating a real format instance when needed.
182
402
format._creating_bzrdir = self
183
return RemoteRepository(self, format)
403
remote_repo = RemoteRepository(self, format)
404
format._creating_repo = remote_repo
405
if real_repo is not None:
406
remote_repo._set_real_repository(real_repo)
185
409
raise errors.NoRepositoryPresent(self)
411
def has_workingtree(self):
412
if self._has_working_tree is None:
414
self._has_working_tree = self._real_bzrdir.has_workingtree()
415
return self._has_working_tree
187
417
def open_workingtree(self, recommend_upgrade=True):
189
if self._real_bzrdir.has_workingtree():
418
if self.has_workingtree():
190
419
raise errors.NotLocalUrl(self.root_transport)
192
421
raise errors.NoWorkingTree(self.root_transport.base)
237
468
the attributes rich_root_data and supports_tree_reference are set
238
469
on a per instance basis, and are not set (and should not be) at
472
:ivar _custom_format: If set, a specific concrete repository format that
473
will be used when initializing a repository with this
474
RemoteRepositoryFormat.
475
:ivar _creating_repo: If set, the repository object that this
476
RemoteRepositoryFormat was created for: it can be called into
477
to obtain data like the network name.
242
480
_matchingbzrdir = RemoteBzrDirFormat()
244
def initialize(self, a_bzrdir, shared=False):
245
if not isinstance(a_bzrdir, RemoteBzrDir):
483
repository.RepositoryFormat.__init__(self)
484
self._custom_format = None
485
self._network_name = None
486
self._creating_bzrdir = None
487
self._supports_chks = None
488
self._supports_external_lookups = None
489
self._supports_tree_reference = None
490
self._rich_root_data = None
493
return "%s(_network_name=%r)" % (self.__class__.__name__,
497
def fast_deltas(self):
499
return self._custom_format.fast_deltas
502
def rich_root_data(self):
503
if self._rich_root_data is None:
505
self._rich_root_data = self._custom_format.rich_root_data
506
return self._rich_root_data
509
def supports_chks(self):
510
if self._supports_chks is None:
512
self._supports_chks = self._custom_format.supports_chks
513
return self._supports_chks
516
def supports_external_lookups(self):
517
if self._supports_external_lookups is None:
519
self._supports_external_lookups = \
520
self._custom_format.supports_external_lookups
521
return self._supports_external_lookups
524
def supports_tree_reference(self):
525
if self._supports_tree_reference is None:
527
self._supports_tree_reference = \
528
self._custom_format.supports_tree_reference
529
return self._supports_tree_reference
531
def _vfs_initialize(self, a_bzrdir, shared):
532
"""Helper for common code in initialize."""
533
if self._custom_format:
534
# Custom format requested
535
result = self._custom_format.initialize(a_bzrdir, shared=shared)
536
elif self._creating_bzrdir is not None:
537
# Use the format that the repository we were created to back
246
539
prior_repo = self._creating_bzrdir.open_repository()
247
540
prior_repo._ensure_real()
248
return prior_repo._real_repository._format.initialize(
541
result = prior_repo._real_repository._format.initialize(
249
542
a_bzrdir, shared=shared)
250
return a_bzrdir.create_repository(shared=shared)
544
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
545
# support remote initialization.
546
# We delegate to a real object at this point (as RemoteBzrDir
547
# delegate to the repository format which would lead to infinite
548
# recursion if we just called a_bzrdir.create_repository.
549
a_bzrdir._ensure_real()
550
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
551
if not isinstance(result, RemoteRepository):
552
return self.open(a_bzrdir)
556
def initialize(self, a_bzrdir, shared=False):
557
# Being asked to create on a non RemoteBzrDir:
558
if not isinstance(a_bzrdir, RemoteBzrDir):
559
return self._vfs_initialize(a_bzrdir, shared)
560
medium = a_bzrdir._client._medium
561
if medium._is_remote_before((1, 13)):
562
return self._vfs_initialize(a_bzrdir, shared)
563
# Creating on a remote bzr dir.
564
# 1) get the network name to use.
565
if self._custom_format:
566
network_name = self._custom_format.network_name()
567
elif self._network_name:
568
network_name = self._network_name
570
# Select the current bzrlib default and ask for that.
571
reference_bzrdir_format = bzrdir.format_registry.get('default')()
572
reference_format = reference_bzrdir_format.repository_format
573
network_name = reference_format.network_name()
574
# 2) try direct creation via RPC
575
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
576
verb = 'BzrDir.create_repository'
582
response = a_bzrdir._call(verb, path, network_name, shared_str)
583
except errors.UnknownSmartMethod:
584
# Fallback - use vfs methods
585
medium._remember_remote_is_before((1, 13))
586
return self._vfs_initialize(a_bzrdir, shared)
588
# Turn the response into a RemoteRepository object.
589
format = response_tuple_to_repo_format(response[1:])
590
# Used to support creating a real format instance when needed.
591
format._creating_bzrdir = a_bzrdir
592
remote_repo = RemoteRepository(a_bzrdir, format)
593
format._creating_repo = remote_repo
252
596
def open(self, a_bzrdir):
253
597
if not isinstance(a_bzrdir, RemoteBzrDir):
254
598
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
255
599
return a_bzrdir.open_repository()
601
def _ensure_real(self):
602
if self._custom_format is None:
603
self._custom_format = repository.network_format_registry.get(
607
def _fetch_order(self):
609
return self._custom_format._fetch_order
612
def _fetch_uses_deltas(self):
614
return self._custom_format._fetch_uses_deltas
617
def _fetch_reconcile(self):
619
return self._custom_format._fetch_reconcile
257
621
def get_format_description(self):
258
return 'bzr remote repository'
623
return 'Remote: ' + self._custom_format.get_format_description()
260
625
def __eq__(self, other):
261
return self.__class__ == other.__class__
263
def check_conversion_target(self, target_format):
264
if self.rich_root_data and not target_format.rich_root_data:
265
raise errors.BadConversionTarget(
266
'Does not support rich root data.', target_format)
267
if (self.supports_tree_reference and
268
not getattr(target_format, 'supports_tree_reference', False)):
269
raise errors.BadConversionTarget(
270
'Does not support nested trees', target_format)
273
class RemoteRepository(object):
626
return self.__class__ is other.__class__
628
def network_name(self):
629
if self._network_name:
630
return self._network_name
631
self._creating_repo._ensure_real()
632
return self._creating_repo._real_repository._format.network_name()
635
def pack_compresses(self):
637
return self._custom_format.pack_compresses
640
def _serializer(self):
642
return self._custom_format._serializer
645
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
274
646
"""Repository accessed over rpc.
276
648
For the moment most operations are performed using local transport-backed
345
731
self._ensure_real()
346
732
return self._real_repository.commit_write_group()
734
def resume_write_group(self, tokens):
736
return self._real_repository.resume_write_group(tokens)
738
def suspend_write_group(self):
740
return self._real_repository.suspend_write_group()
742
def get_missing_parent_inventories(self, check_for_missing_texts=True):
744
return self._real_repository.get_missing_parent_inventories(
745
check_for_missing_texts=check_for_missing_texts)
747
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
749
return self._real_repository.get_rev_id_for_revno(
752
def get_rev_id_for_revno(self, revno, known_pair):
753
"""See Repository.get_rev_id_for_revno."""
754
path = self.bzrdir._path_for_remote_call(self._client)
756
if self._client._medium._is_remote_before((1, 17)):
757
return self._get_rev_id_for_revno_vfs(revno, known_pair)
758
response = self._call(
759
'Repository.get_rev_id_for_revno', path, revno, known_pair)
760
except errors.UnknownSmartMethod:
761
self._client._medium._remember_remote_is_before((1, 17))
762
return self._get_rev_id_for_revno_vfs(revno, known_pair)
763
if response[0] == 'ok':
764
return True, response[1]
765
elif response[0] == 'history-incomplete':
766
known_pair = response[1:3]
767
for fallback in self._fallback_repositories:
768
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
773
# Not found in any fallbacks
774
return False, known_pair
776
raise errors.UnexpectedSmartServerResponse(response)
348
778
def _ensure_real(self):
349
779
"""Ensure that there is a _real_repository set.
351
781
Used before calls to self._real_repository.
783
Note that _ensure_real causes many roundtrips to the server which are
784
not desirable, and prevents the use of smart one-roundtrip RPC's to
785
perform complex operations (such as accessing parent data, streaming
786
revisions etc). Adding calls to _ensure_real should only be done when
787
bringing up new functionality, adding fallbacks for smart methods that
788
require a fallback path, and never to replace an existing smart method
789
invocation. If in doubt chat to the bzr network team.
353
791
if self._real_repository is None:
792
if 'hpssvfs' in debug.debug_flags:
794
warning('VFS Repository access triggered\n%s',
795
''.join(traceback.format_stack()))
796
self._unstacked_provider.missing_keys.clear()
354
797
self.bzrdir._ensure_real()
355
798
self._set_real_repository(
356
799
self.bzrdir._real_bzrdir.open_repository())
413
848
for line in lines:
414
849
d = tuple(line.split())
415
850
revision_graph[d[0]] = d[1:]
417
852
return revision_graph
855
"""See Repository._get_sink()."""
856
return RemoteStreamSink(self)
858
def _get_source(self, to_format):
859
"""Return a source for streaming from this repository."""
860
return RemoteStreamSource(self, to_format)
419
863
def has_revision(self, revision_id):
420
"""See Repository.has_revision()."""
421
if revision_id == NULL_REVISION:
422
# The null revision is always present.
424
path = self.bzrdir._path_for_remote_call(self._client)
425
response = self._client.call(
426
'Repository.has_revision', path, revision_id)
427
if response[0] not in ('yes', 'no'):
428
raise errors.UnexpectedSmartServerResponse(response)
429
if response[0] == 'yes':
431
for fallback_repo in self._fallback_repositories:
432
if fallback_repo.has_revision(revision_id):
864
"""True if this repository has a copy of the revision."""
865
# Copy of bzrlib.repository.Repository.has_revision
866
return revision_id in self.has_revisions((revision_id,))
436
869
def has_revisions(self, revision_ids):
437
"""See Repository.has_revisions()."""
438
# FIXME: This does many roundtrips, particularly when there are
439
# fallback repositories. -- mbp 20080905
441
for revision_id in revision_ids:
442
if self.has_revision(revision_id):
443
result.add(revision_id)
870
"""Probe to find out the presence of multiple revisions.
872
:param revision_ids: An iterable of revision_ids.
873
:return: A set of the revision_ids that were present.
875
# Copy of bzrlib.repository.Repository.has_revisions
876
parent_map = self.get_parent_map(revision_ids)
877
result = set(parent_map)
878
if _mod_revision.NULL_REVISION in revision_ids:
879
result.add(_mod_revision.NULL_REVISION)
882
def _has_same_fallbacks(self, other_repo):
883
"""Returns true if the repositories have the same fallbacks."""
884
# XXX: copied from Repository; it should be unified into a base class
885
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
886
my_fb = self._fallback_repositories
887
other_fb = other_repo._fallback_repositories
888
if len(my_fb) != len(other_fb):
890
for f, g in zip(my_fb, other_fb):
891
if not f.has_same_location(g):
446
895
def has_same_location(self, other):
447
return (self.__class__ == other.__class__ and
896
# TODO: Move to RepositoryBase and unify with the regular Repository
897
# one; unfortunately the tests rely on slightly different behaviour at
898
# present -- mbp 20090710
899
return (self.__class__ is other.__class__ and
448
900
self.bzrdir.transport.base == other.bzrdir.transport.base)
450
902
def get_graph(self, other_repository=None):
451
903
"""Return the graph for this repository format"""
452
parents_provider = self
453
if (other_repository is not None and
454
other_repository.bzrdir.transport.base !=
455
self.bzrdir.transport.base):
456
parents_provider = graph._StackedParentsProvider(
457
[parents_provider, other_repository._make_parents_provider()])
904
parents_provider = self._make_parents_provider(other_repository)
458
905
return graph.Graph(parents_provider)
908
def get_known_graph_ancestry(self, revision_ids):
909
"""Return the known graph for a set of revision ids and their ancestors.
911
st = static_tuple.StaticTuple
912
revision_keys = [st(r_id).intern() for r_id in revision_ids]
913
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
914
return graph.GraphThunkIdsToKeys(known_graph)
460
916
def gather_stats(self, revid=None, committers=None):
461
917
"""See Repository.gather_stats()."""
462
918
path = self.bzrdir._path_for_remote_call(self._client)
723
1214
def add_fallback_repository(self, repository):
724
1215
"""Add a repository to use for looking up data not held locally.
726
1217
:param repository: A repository.
728
# XXX: At the moment the RemoteRepository will allow fallbacks
729
# unconditionally - however, a _real_repository will usually exist,
730
# and may raise an error if it's not accommodated by the underlying
731
# format. Eventually we should check when opening the repository
732
# whether it's willing to allow them or not.
1219
if not self._format.supports_external_lookups:
1220
raise errors.UnstackableRepositoryFormat(
1221
self._format.network_name(), self.base)
734
1222
# We need to accumulate additional repositories here, to pass them in
735
1223
# on various RPC's.
1225
if self.is_locked():
1226
# We will call fallback.unlock() when we transition to the unlocked
1227
# state, so always add a lock here. If a caller passes us a locked
1228
# repository, they are responsible for unlocking it later.
1229
repository.lock_read()
736
1230
self._fallback_repositories.append(repository)
737
# They are also seen by the fallback repository. If it doesn't exist
738
# yet they'll be added then. This implicitly copies them.
1231
# If self._real_repository was parameterised already (e.g. because a
1232
# _real_branch had its get_stacked_on_url method called), then the
1233
# repository to be added may already be in the _real_repositories list.
1234
if self._real_repository is not None:
1235
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1236
self._real_repository._fallback_repositories]
1237
if repository.bzrdir.root_transport.base not in fallback_locations:
1238
self._real_repository.add_fallback_repository(repository)
741
1240
def add_inventory(self, revid, inv, parents):
742
1241
self._ensure_real()
743
1242
return self._real_repository.add_inventory(revid, inv, parents)
1244
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1245
parents, basis_inv=None, propagate_caches=False):
1247
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1248
delta, new_revision_id, parents, basis_inv=basis_inv,
1249
propagate_caches=propagate_caches)
745
1251
def add_revision(self, rev_id, rev, inv=None, config=None):
746
1252
self._ensure_real()
747
1253
return self._real_repository.add_revision(
828
1363
self._ensure_real()
829
1364
return self._real_repository._get_versioned_file_checker(
830
1365
revisions, revision_versions_cache)
832
1367
def iter_files_bytes(self, desired_files):
833
1368
"""See Repository.iter_file_bytes.
835
1370
self._ensure_real()
836
1371
return self._real_repository.iter_files_bytes(desired_files)
839
def _fetch_order(self):
840
"""Decorate the real repository for now.
842
In the long term getting this back from the remote repository as part
843
of open would be more efficient.
846
return self._real_repository._fetch_order
849
def _fetch_uses_deltas(self):
850
"""Decorate the real repository for now.
852
In the long term getting this back from the remote repository as part
853
of open would be more efficient.
856
return self._real_repository._fetch_uses_deltas
859
def _fetch_reconcile(self):
860
"""Decorate the real repository for now.
862
In the long term getting this back from the remote repository as part
863
of open would be more efficient.
866
return self._real_repository._fetch_reconcile
868
def get_parent_map(self, keys):
1373
def get_parent_map(self, revision_ids):
869
1374
"""See bzrlib.Graph.get_parent_map()."""
870
# Hack to build up the caching logic.
871
ancestry = self._parents_map
873
# Repository is not locked, so there's no cache.
874
missing_revisions = set(keys)
877
missing_revisions = set(key for key in keys if key not in ancestry)
878
if missing_revisions:
879
parent_map = self._get_parent_map(missing_revisions)
880
if 'hpss' in debug.debug_flags:
881
mutter('retransmitted revisions: %d of %d',
882
len(set(ancestry).intersection(parent_map)),
884
ancestry.update(parent_map)
885
present_keys = [k for k in keys if k in ancestry]
886
if 'hpss' in debug.debug_flags:
887
if self._requested_parents is not None and len(ancestry) != 0:
888
self._requested_parents.update(present_keys)
889
mutter('Current RemoteRepository graph hit rate: %d%%',
890
100.0 * len(self._requested_parents) / len(ancestry))
891
return dict((k, ancestry[k]) for k in present_keys)
1375
return self._make_parents_provider().get_parent_map(revision_ids)
893
def _get_parent_map(self, keys):
1377
def _get_parent_map_rpc(self, keys):
894
1378
"""Helper for get_parent_map that performs the RPC."""
895
1379
medium = self._client._medium
896
1380
if medium._is_remote_before((1, 2)):
897
1381
# We already found out that the server can't understand
898
1382
# Repository.get_parent_map requests, so just fetch the whole
900
# XXX: Note that this will issue a deprecation warning. This is ok
901
# :- its because we're working with a deprecated server anyway, and
902
# the user will almost certainly have seen a warning about the
903
# server version already.
904
rg = self.get_revision_graph()
905
# There is an api discrepency between get_parent_map and
1385
# Note that this reads the whole graph, when only some keys are
1386
# wanted. On this old server there's no way (?) to get them all
1387
# in one go, and the user probably will have seen a warning about
1388
# the server being old anyhow.
1389
rg = self._get_revision_graph(None)
1390
# There is an API discrepancy between get_parent_map and
906
1391
# get_revision_graph. Specifically, a "key:()" pair in
907
1392
# get_revision_graph just means a node has no parents. For
908
1393
# "get_parent_map" it means the node is a ghost. So fix up the
1199
1718
:param recipe: A search recipe (start, stop, count).
1200
1719
:return: Serialised bytes.
1202
start_keys = ' '.join(recipe[0])
1203
stop_keys = ' '.join(recipe[1])
1204
count = str(recipe[2])
1721
start_keys = ' '.join(recipe[1])
1722
stop_keys = ' '.join(recipe[2])
1723
count = str(recipe[3])
1205
1724
return '\n'.join((start_keys, stop_keys, count))
1726
def _serialise_search_result(self, search_result):
1727
if isinstance(search_result, graph.PendingAncestryResult):
1728
parts = ['ancestry-of']
1729
parts.extend(search_result.heads)
1731
recipe = search_result.get_recipe()
1732
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1733
return '\n'.join(parts)
1736
path = self.bzrdir._path_for_remote_call(self._client)
1738
response = self._call('PackRepository.autopack', path)
1739
except errors.UnknownSmartMethod:
1741
self._real_repository._pack_collection.autopack()
1744
if response[0] != 'ok':
1745
raise errors.UnexpectedSmartServerResponse(response)
1748
class RemoteStreamSink(repository.StreamSink):
1750
def _insert_real(self, stream, src_format, resume_tokens):
1751
self.target_repo._ensure_real()
1752
sink = self.target_repo._real_repository._get_sink()
1753
result = sink.insert_stream(stream, src_format, resume_tokens)
1755
self.target_repo.autopack()
1758
def insert_stream(self, stream, src_format, resume_tokens):
1759
target = self.target_repo
1760
target._unstacked_provider.missing_keys.clear()
1761
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1762
if target._lock_token:
1763
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1764
lock_args = (target._lock_token or '',)
1766
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1768
client = target._client
1769
medium = client._medium
1770
path = target.bzrdir._path_for_remote_call(client)
1771
# Probe for the verb to use with an empty stream before sending the
1772
# real stream to it. We do this both to avoid the risk of sending a
1773
# large request that is then rejected, and because we don't want to
1774
# implement a way to buffer, rewind, or restart the stream.
1776
for verb, required_version in candidate_calls:
1777
if medium._is_remote_before(required_version):
1780
# We've already done the probing (and set _is_remote_before) on
1781
# a previous insert.
1784
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1786
response = client.call_with_body_stream(
1787
(verb, path, '') + lock_args, byte_stream)
1788
except errors.UnknownSmartMethod:
1789
medium._remember_remote_is_before(required_version)
1795
return self._insert_real(stream, src_format, resume_tokens)
1796
self._last_inv_record = None
1797
self._last_substream = None
1798
if required_version < (1, 19):
1799
# Remote side doesn't support inventory deltas. Wrap the stream to
1800
# make sure we don't send any. If the stream contains inventory
1801
# deltas we'll interrupt the smart insert_stream request and
1803
stream = self._stop_stream_if_inventory_delta(stream)
1804
byte_stream = smart_repo._stream_to_byte_stream(
1806
resume_tokens = ' '.join(resume_tokens)
1807
response = client.call_with_body_stream(
1808
(verb, path, resume_tokens) + lock_args, byte_stream)
1809
if response[0][0] not in ('ok', 'missing-basis'):
1810
raise errors.UnexpectedSmartServerResponse(response)
1811
if self._last_substream is not None:
1812
# The stream included an inventory-delta record, but the remote
1813
# side isn't new enough to support them. So we need to send the
1814
# rest of the stream via VFS.
1815
self.target_repo.refresh_data()
1816
return self._resume_stream_with_vfs(response, src_format)
1817
if response[0][0] == 'missing-basis':
1818
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1819
resume_tokens = tokens
1820
return resume_tokens, set(missing_keys)
1822
self.target_repo.refresh_data()
1825
def _resume_stream_with_vfs(self, response, src_format):
1826
"""Resume sending a stream via VFS, first resending the record and
1827
substream that couldn't be sent via an insert_stream verb.
1829
if response[0][0] == 'missing-basis':
1830
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1831
# Ignore missing_keys, we haven't finished inserting yet
1834
def resume_substream():
1835
# Yield the substream that was interrupted.
1836
for record in self._last_substream:
1838
self._last_substream = None
1839
def resume_stream():
1840
# Finish sending the interrupted substream
1841
yield ('inventory-deltas', resume_substream())
1842
# Then simply continue sending the rest of the stream.
1843
for substream_kind, substream in self._last_stream:
1844
yield substream_kind, substream
1845
return self._insert_real(resume_stream(), src_format, tokens)
1847
def _stop_stream_if_inventory_delta(self, stream):
1848
"""Normally this just lets the original stream pass-through unchanged.
1850
However if any 'inventory-deltas' substream occurs it will stop
1851
streaming, and store the interrupted substream and stream in
1852
self._last_substream and self._last_stream so that the stream can be
1853
resumed by _resume_stream_with_vfs.
1856
stream_iter = iter(stream)
1857
for substream_kind, substream in stream_iter:
1858
if substream_kind == 'inventory-deltas':
1859
self._last_substream = substream
1860
self._last_stream = stream_iter
1863
yield substream_kind, substream
1866
class RemoteStreamSource(repository.StreamSource):
1867
"""Stream data from a remote server."""
1869
def get_stream(self, search):
1870
if (self.from_repository._fallback_repositories and
1871
self.to_format._fetch_order == 'topological'):
1872
return self._real_stream(self.from_repository, search)
1875
repos = [self.from_repository]
1881
repos.extend(repo._fallback_repositories)
1882
sources.append(repo)
1883
return self.missing_parents_chain(search, sources)
1885
def get_stream_for_missing_keys(self, missing_keys):
1886
self.from_repository._ensure_real()
1887
real_repo = self.from_repository._real_repository
1888
real_source = real_repo._get_source(self.to_format)
1889
return real_source.get_stream_for_missing_keys(missing_keys)
1891
def _real_stream(self, repo, search):
1892
"""Get a stream for search from repo.
1894
This never called RemoteStreamSource.get_stream, and is a heler
1895
for RemoteStreamSource._get_stream to allow getting a stream
1896
reliably whether fallback back because of old servers or trying
1897
to stream from a non-RemoteRepository (which the stacked support
1900
source = repo._get_source(self.to_format)
1901
if isinstance(source, RemoteStreamSource):
1903
source = repo._real_repository._get_source(self.to_format)
1904
return source.get_stream(search)
1906
def _get_stream(self, repo, search):
1907
"""Core worker to get a stream from repo for search.
1909
This is used by both get_stream and the stacking support logic. It
1910
deliberately gets a stream for repo which does not need to be
1911
self.from_repository. In the event that repo is not Remote, or
1912
cannot do a smart stream, a fallback is made to the generic
1913
repository._get_stream() interface, via self._real_stream.
1915
In the event of stacking, streams from _get_stream will not
1916
contain all the data for search - this is normal (see get_stream).
1918
:param repo: A repository.
1919
:param search: A search.
1921
# Fallbacks may be non-smart
1922
if not isinstance(repo, RemoteRepository):
1923
return self._real_stream(repo, search)
1924
client = repo._client
1925
medium = client._medium
1926
path = repo.bzrdir._path_for_remote_call(client)
1927
search_bytes = repo._serialise_search_result(search)
1928
args = (path, self.to_format.network_name())
1930
('Repository.get_stream_1.19', (1, 19)),
1931
('Repository.get_stream', (1, 13))]
1933
for verb, version in candidate_verbs:
1934
if medium._is_remote_before(version):
1937
response = repo._call_with_body_bytes_expecting_body(
1938
verb, args, search_bytes)
1939
except errors.UnknownSmartMethod:
1940
medium._remember_remote_is_before(version)
1942
response_tuple, response_handler = response
1946
return self._real_stream(repo, search)
1947
if response_tuple[0] != 'ok':
1948
raise errors.UnexpectedSmartServerResponse(response_tuple)
1949
byte_stream = response_handler.read_streamed_body()
1950
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1951
if src_format.network_name() != repo._format.network_name():
1952
raise AssertionError(
1953
"Mismatched RemoteRepository and stream src %r, %r" % (
1954
src_format.network_name(), repo._format.network_name()))
1957
def missing_parents_chain(self, search, sources):
1958
"""Chain multiple streams together to handle stacking.
1960
:param search: The overall search to satisfy with streams.
1961
:param sources: A list of Repository objects to query.
1963
self.from_serialiser = self.from_repository._format._serializer
1964
self.seen_revs = set()
1965
self.referenced_revs = set()
1966
# If there are heads in the search, or the key count is > 0, we are not
1968
while not search.is_empty() and len(sources) > 1:
1969
source = sources.pop(0)
1970
stream = self._get_stream(source, search)
1971
for kind, substream in stream:
1972
if kind != 'revisions':
1973
yield kind, substream
1975
yield kind, self.missing_parents_rev_handler(substream)
1976
search = search.refine(self.seen_revs, self.referenced_revs)
1977
self.seen_revs = set()
1978
self.referenced_revs = set()
1979
if not search.is_empty():
1980
for kind, stream in self._get_stream(sources[0], search):
1983
def missing_parents_rev_handler(self, substream):
1984
for content in substream:
1985
revision_bytes = content.get_bytes_as('fulltext')
1986
revision = self.from_serialiser.read_revision_from_string(
1988
self.seen_revs.add(content.key[-1])
1989
self.referenced_revs.update(revision.parent_ids)
1208
1993
class RemoteBranchLockableFiles(LockableFiles):
1209
1994
"""A 'LockableFiles' implementation that talks to a smart server.
1211
1996
This is not a public interface class.
1228
2013
class RemoteBranchFormat(branch.BranchFormat):
2015
def __init__(self, network_name=None):
2016
super(RemoteBranchFormat, self).__init__()
2017
self._matchingbzrdir = RemoteBzrDirFormat()
2018
self._matchingbzrdir.set_branch_format(self)
2019
self._custom_format = None
2020
self._network_name = network_name
1230
2022
def __eq__(self, other):
1231
return (isinstance(other, RemoteBranchFormat) and
2023
return (isinstance(other, RemoteBranchFormat) and
1232
2024
self.__dict__ == other.__dict__)
2026
def _ensure_real(self):
2027
if self._custom_format is None:
2028
self._custom_format = branch.network_format_registry.get(
1234
2031
def get_format_description(self):
1235
return 'Remote BZR Branch'
1237
def get_format_string(self):
1238
return 'Remote BZR Branch'
1240
def open(self, a_bzrdir):
1241
return a_bzrdir.open_branch()
1243
def initialize(self, a_bzrdir):
1244
return a_bzrdir.create_branch()
2033
return 'Remote: ' + self._custom_format.get_format_description()
2035
def network_name(self):
2036
return self._network_name
2038
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2039
return a_bzrdir.open_branch(name=name,
2040
ignore_fallbacks=ignore_fallbacks)
2042
def _vfs_initialize(self, a_bzrdir, name):
2043
# Initialisation when using a local bzrdir object, or a non-vfs init
2044
# method is not available on the server.
2045
# self._custom_format is always set - the start of initialize ensures
2047
if isinstance(a_bzrdir, RemoteBzrDir):
2048
a_bzrdir._ensure_real()
2049
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2052
# We assume the bzrdir is parameterised; it may not be.
2053
result = self._custom_format.initialize(a_bzrdir, name)
2054
if (isinstance(a_bzrdir, RemoteBzrDir) and
2055
not isinstance(result, RemoteBranch)):
2056
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2060
def initialize(self, a_bzrdir, name=None):
2061
# 1) get the network name to use.
2062
if self._custom_format:
2063
network_name = self._custom_format.network_name()
2065
# Select the current bzrlib default and ask for that.
2066
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2067
reference_format = reference_bzrdir_format.get_branch_format()
2068
self._custom_format = reference_format
2069
network_name = reference_format.network_name()
2070
# Being asked to create on a non RemoteBzrDir:
2071
if not isinstance(a_bzrdir, RemoteBzrDir):
2072
return self._vfs_initialize(a_bzrdir, name=name)
2073
medium = a_bzrdir._client._medium
2074
if medium._is_remote_before((1, 13)):
2075
return self._vfs_initialize(a_bzrdir, name=name)
2076
# Creating on a remote bzr dir.
2077
# 2) try direct creation via RPC
2078
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2079
if name is not None:
2080
# XXX JRV20100304: Support creating colocated branches
2081
raise errors.NoColocatedBranchSupport(self)
2082
verb = 'BzrDir.create_branch'
2084
response = a_bzrdir._call(verb, path, network_name)
2085
except errors.UnknownSmartMethod:
2086
# Fallback - use vfs methods
2087
medium._remember_remote_is_before((1, 13))
2088
return self._vfs_initialize(a_bzrdir, name=name)
2089
if response[0] != 'ok':
2090
raise errors.UnexpectedSmartServerResponse(response)
2091
# Turn the response into a RemoteRepository object.
2092
format = RemoteBranchFormat(network_name=response[1])
2093
repo_format = response_tuple_to_repo_format(response[3:])
2094
if response[2] == '':
2095
repo_bzrdir = a_bzrdir
2097
repo_bzrdir = RemoteBzrDir(
2098
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2100
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2101
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2102
format=format, setup_stacking=False, name=name)
2103
# XXX: We know this is a new branch, so it must have revno 0, revid
2104
# NULL_REVISION. Creating the branch locked would make this be unable
2105
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2106
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2107
return remote_branch
2109
def make_tags(self, branch):
2111
return self._custom_format.make_tags(branch)
1246
2113
def supports_tags(self):
1247
2114
# Remote branches might support tags, but we won't know until we
1248
2115
# access the real remote branch.
1252
class RemoteBranch(branch.Branch):
2117
return self._custom_format.supports_tags()
2119
def supports_stacking(self):
2121
return self._custom_format.supports_stacking()
2123
def supports_set_append_revisions_only(self):
2125
return self._custom_format.supports_set_append_revisions_only()
2128
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
1253
2129
"""Branch stored on a server accessed by HPSS RPC.
1255
2131
At the moment most operations are mapped down to simple file operations.
1258
2134
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2135
_client=None, format=None, setup_stacking=True, name=None):
1260
2136
"""Create a RemoteBranch instance.
1262
2138
:param real_branch: An optional local implementation of the branch
1263
2139
format, usually accessing the data via the VFS.
1264
2140
:param _client: Private parameter for testing.
2141
:param format: A RemoteBranchFormat object, None to create one
2142
automatically. If supplied it should have a network_name already
2144
:param setup_stacking: If True make an RPC call to determine the
2145
stacked (or not) status of the branch. If False assume the branch
2147
:param name: Colocated branch name
1266
2149
# We intentionally don't call the parent class's __init__, because it
1267
2150
# will try to assign to self.tags, which is a property in this subclass.
1268
2151
# And the parent's __init__ doesn't do much anyway.
1269
self._revision_id_to_revno_cache = None
1270
self._revision_history_cache = None
1271
self._last_revision_info_cache = None
1272
2152
self.bzrdir = remote_bzrdir
1273
2153
if _client is not None:
1274
2154
self._client = _client
1560
2523
def _set_last_revision_descendant(self, revision_id, other_branch,
1561
2524
allow_diverged=False, allow_overwrite_descendant=False):
1563
response = self._client.call('Branch.set_last_revision_ex',
1564
self._remote_path(), self._lock_token, self._repo_lock_token, revision_id,
1565
int(allow_diverged), int(allow_overwrite_descendant))
1566
except errors.ErrorFromSmartServer, err:
1567
self._translate_error(err, other_branch=other_branch)
2525
# This performs additional work to meet the hook contract; while its
2526
# undesirable, we have to synthesise the revno to call the hook, and
2527
# not calling the hook is worse as it means changes can't be prevented.
2528
# Having calculated this though, we can't just call into
2529
# set_last_revision_info as a simple call, because there is a set_rh
2530
# hook that some folk may still be using.
2531
old_revno, old_revid = self.last_revision_info()
2532
history = self._lefthand_history(revision_id)
2533
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2534
err_context = {'other_branch': other_branch}
2535
response = self._call('Branch.set_last_revision_ex',
2536
self._remote_path(), self._lock_token, self._repo_lock_token,
2537
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1568
2539
self._clear_cached_state()
1569
2540
if len(response) != 3 and response[0] != 'ok':
1570
2541
raise errors.UnexpectedSmartServerResponse(response)
1571
2542
new_revno, new_revision_id = response[1:]
1572
2543
self._last_revision_info_cache = new_revno, new_revision_id
2544
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1573
2545
if self._real_branch is not None:
1574
2546
cache = new_revno, new_revision_id
1575
2547
self._real_branch._last_revision_info_cache = cache
1577
2549
def _set_last_revision(self, revision_id):
2550
old_revno, old_revid = self.last_revision_info()
2551
# This performs additional work to meet the hook contract; while its
2552
# undesirable, we have to synthesise the revno to call the hook, and
2553
# not calling the hook is worse as it means changes can't be prevented.
2554
# Having calculated this though, we can't just call into
2555
# set_last_revision_info as a simple call, because there is a set_rh
2556
# hook that some folk may still be using.
2557
history = self._lefthand_history(revision_id)
2558
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1578
2559
self._clear_cached_state()
1580
response = self._client.call('Branch.set_last_revision',
1581
self._remote_path(), self._lock_token, self._repo_lock_token, revision_id)
1582
except errors.ErrorFromSmartServer, err:
1583
self._translate_error(err)
2560
response = self._call('Branch.set_last_revision',
2561
self._remote_path(), self._lock_token, self._repo_lock_token,
1584
2563
if response != ('ok',):
1585
2564
raise errors.UnexpectedSmartServerResponse(response)
2565
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1587
2567
@needs_write_lock
1588
2568
def set_revision_history(self, rev_history):
1595
2575
rev_id = rev_history[-1]
1596
2576
self._set_last_revision(rev_id)
2577
for hook in branch.Branch.hooks['set_rh']:
2578
hook(self, rev_history)
1597
2579
self._cache_revision_history(rev_history)
1599
def get_parent(self):
1601
return self._real_branch.get_parent()
1603
def set_parent(self, url):
1605
return self._real_branch.set_parent(url)
1607
def set_stacked_on_url(self, stacked_location):
1608
"""Set the URL this branch is stacked against.
1610
:raises UnstackableBranchFormat: If the branch does not support
1612
:raises UnstackableRepositoryFormat: If the repository does not support
1616
return self._real_branch.set_stacked_on_url(stacked_location)
1618
def sprout(self, to_bzrdir, revision_id=None):
1619
# Like Branch.sprout, except that it sprouts a branch in the default
1620
# format, because RemoteBranches can't be created at arbitrary URLs.
1621
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1622
# to_bzrdir.create_branch...
1624
result = self._real_branch._format.initialize(to_bzrdir)
1625
self.copy_content_into(result, revision_id=revision_id)
1626
result.set_parent(self.bzrdir.root_transport.base)
2581
def _get_parent_location(self):
2582
medium = self._client._medium
2583
if medium._is_remote_before((1, 13)):
2584
return self._vfs_get_parent_location()
2586
response = self._call('Branch.get_parent', self._remote_path())
2587
except errors.UnknownSmartMethod:
2588
medium._remember_remote_is_before((1, 13))
2589
return self._vfs_get_parent_location()
2590
if len(response) != 1:
2591
raise errors.UnexpectedSmartServerResponse(response)
2592
parent_location = response[0]
2593
if parent_location == '':
2595
return parent_location
2597
def _vfs_get_parent_location(self):
2599
return self._real_branch._get_parent_location()
2601
def _set_parent_location(self, url):
2602
medium = self._client._medium
2603
if medium._is_remote_before((1, 15)):
2604
return self._vfs_set_parent_location(url)
2606
call_url = url or ''
2607
if type(call_url) is not str:
2608
raise AssertionError('url must be a str or None (%s)' % url)
2609
response = self._call('Branch.set_parent_location',
2610
self._remote_path(), self._lock_token, self._repo_lock_token,
2612
except errors.UnknownSmartMethod:
2613
medium._remember_remote_is_before((1, 15))
2614
return self._vfs_set_parent_location(url)
2616
raise errors.UnexpectedSmartServerResponse(response)
2618
def _vfs_set_parent_location(self, url):
2620
return self._real_branch._set_parent_location(url)
1629
2622
@needs_write_lock
1630
2623
def pull(self, source, overwrite=False, stop_revision=None,
1686
2683
except errors.UnknownSmartMethod:
1687
2684
medium._remember_remote_is_before((1, 6))
1688
2685
self._clear_cached_state_of_remote_branch_only()
1690
self._real_branch.generate_revision_history(
1691
revision_id, last_rev=last_rev, other_branch=other_branch)
1696
return self._real_branch.tags
2686
self.set_revision_history(self._lefthand_history(revision_id,
2687
last_rev=last_rev,other_branch=other_branch))
1698
2689
def set_push_location(self, location):
1699
2690
self._ensure_real()
1700
2691
return self._real_branch.set_push_location(location)
1703
def update_revisions(self, other, stop_revision=None, overwrite=False,
1705
"""See Branch.update_revisions."""
2694
class RemoteConfig(object):
2695
"""A Config that reads and writes from smart verbs.
2697
It is a low-level object that considers config data to be name/value pairs
2698
that may be associated with a section. Assigning meaning to the these
2699
values is done at higher levels like bzrlib.config.TreeConfig.
2702
def get_option(self, name, section=None, default=None):
2703
"""Return the value associated with a named option.
2705
:param name: The name of the value
2706
:param section: The section the option is in (if any)
2707
:param default: The value to return if the value is not set
2708
:return: The value or default value
1708
if stop_revision is None:
1709
stop_revision = other.last_revision()
1710
if revision.is_null(stop_revision):
1711
# if there are no commits, we're done.
1713
self.fetch(other, stop_revision)
1716
# Just unconditionally set the new revision. We don't care if
1717
# the branches have diverged.
1718
self._set_last_revision(stop_revision)
2711
configobj = self._get_configobj()
2713
section_obj = configobj
1720
medium = self._client._medium
1721
if not medium._is_remote_before((1, 6)):
1723
self._set_last_revision_descendant(stop_revision, other)
1725
except errors.UnknownSmartMethod:
1726
medium._remember_remote_is_before((1, 6))
1727
# Fallback for pre-1.6 servers: check for divergence
1728
# client-side, then do _set_last_revision.
1729
last_rev = revision.ensure_null(self.last_revision())
1731
graph = self.repository.get_graph()
1732
if self._check_if_descendant_or_diverged(
1733
stop_revision, last_rev, graph, other):
1734
# stop_revision is a descendant of last_rev, but we aren't
1735
# overwriting, so we're done.
1737
self._set_last_revision(stop_revision)
2716
section_obj = configobj[section]
2719
return section_obj.get(name, default)
2720
except errors.UnknownSmartMethod:
2721
return self._vfs_get_option(name, section, default)
2723
def _response_to_configobj(self, response):
2724
if len(response[0]) and response[0][0] != 'ok':
2725
raise errors.UnexpectedSmartServerResponse(response)
2726
lines = response[1].read_body_bytes().splitlines()
2727
return config.ConfigObj(lines, encoding='utf-8')
2730
class RemoteBranchConfig(RemoteConfig):
2731
"""A RemoteConfig for Branches."""
2733
def __init__(self, branch):
2734
self._branch = branch
2736
def _get_configobj(self):
2737
path = self._branch._remote_path()
2738
response = self._branch._client.call_expecting_body(
2739
'Branch.get_config_file', path)
2740
return self._response_to_configobj(response)
2742
def set_option(self, value, name, section=None):
2743
"""Set the value associated with a named option.
2745
:param value: The value to set
2746
:param name: The name of the value to set
2747
:param section: The section the option is in (if any)
2749
medium = self._branch._client._medium
2750
if medium._is_remote_before((1, 14)):
2751
return self._vfs_set_option(value, name, section)
2753
path = self._branch._remote_path()
2754
response = self._branch._client.call('Branch.set_config_option',
2755
path, self._branch._lock_token, self._branch._repo_lock_token,
2756
value.encode('utf8'), name, section or '')
2757
except errors.UnknownSmartMethod:
2758
medium._remember_remote_is_before((1, 14))
2759
return self._vfs_set_option(value, name, section)
2761
raise errors.UnexpectedSmartServerResponse(response)
2763
def _real_object(self):
2764
self._branch._ensure_real()
2765
return self._branch._real_branch
2767
def _vfs_set_option(self, value, name, section=None):
2768
return self._real_object()._get_config().set_option(
2769
value, name, section)
2772
class RemoteBzrDirConfig(RemoteConfig):
2773
"""A RemoteConfig for BzrDirs."""
2775
def __init__(self, bzrdir):
2776
self._bzrdir = bzrdir
2778
def _get_configobj(self):
2779
medium = self._bzrdir._client._medium
2780
verb = 'BzrDir.get_config_file'
2781
if medium._is_remote_before((1, 15)):
2782
raise errors.UnknownSmartMethod(verb)
2783
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2784
response = self._bzrdir._call_expecting_body(
2786
return self._response_to_configobj(response)
2788
def _vfs_get_option(self, name, section, default):
2789
return self._real_object()._get_config().get_option(
2790
name, section, default)
2792
def set_option(self, value, name, section=None):
2793
"""Set the value associated with a named option.
2795
:param value: The value to set
2796
:param name: The name of the value to set
2797
:param section: The section the option is in (if any)
2799
return self._real_object()._get_config().set_option(
2800
value, name, section)
2802
def _real_object(self):
2803
self._bzrdir._ensure_real()
2804
return self._bzrdir._real_bzrdir
1742
2808
def _extract_tar(tar, to_dir):