1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
bzrdir as _mod_bzrdir,
30
repository as _mod_repository,
31
revision as _mod_revision,
38
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
39
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
40
from bzrlib.errors import (
44
from bzrlib.lockable_files import LockableFiles
45
from bzrlib.smart import client, vfs, repository as smart_repo
46
from bzrlib.smart.client import _SmartClient
47
from bzrlib.revision import NULL_REVISION
48
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
49
from bzrlib.trace import mutter, note, warning
52
class _RpcHelper(object):
53
"""Mixin class that helps with issuing RPCs."""
55
def _call(self, method, *args, **err_context):
57
return self._client.call(method, *args)
58
except errors.ErrorFromSmartServer, err:
59
self._translate_error(err, **err_context)
61
def _call_expecting_body(self, method, *args, **err_context):
63
return self._client.call_expecting_body(method, *args)
64
except errors.ErrorFromSmartServer, err:
65
self._translate_error(err, **err_context)
67
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
69
return self._client.call_with_body_bytes(method, args, body_bytes)
70
except errors.ErrorFromSmartServer, err:
71
self._translate_error(err, **err_context)
73
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
76
return self._client.call_with_body_bytes_expecting_body(
77
method, args, body_bytes)
78
except errors.ErrorFromSmartServer, err:
79
self._translate_error(err, **err_context)
82
def response_tuple_to_repo_format(response):
83
"""Convert a response tuple describing a repository format to a format."""
84
format = RemoteRepositoryFormat()
85
format._rich_root_data = (response[0] == 'yes')
86
format._supports_tree_reference = (response[1] == 'yes')
87
format._supports_external_lookups = (response[2] == 'yes')
88
format._network_name = response[3]
92
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
93
# does not have to be imported unless a remote format is involved.
95
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
96
"""Format representing bzrdirs accessed via a smart server"""
98
supports_workingtrees = False
101
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
102
# XXX: It's a bit ugly that the network name is here, because we'd
103
# like to believe that format objects are stateless or at least
104
# immutable, However, we do at least avoid mutating the name after
105
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
106
self._network_name = None
109
return "%s(_network_name=%r)" % (self.__class__.__name__,
112
def get_format_description(self):
113
if self._network_name:
114
real_format = controldir.network_format_registry.get(self._network_name)
115
return 'Remote: ' + real_format.get_format_description()
116
return 'bzr remote bzrdir'
118
def get_format_string(self):
119
raise NotImplementedError(self.get_format_string)
121
def network_name(self):
122
if self._network_name:
123
return self._network_name
125
raise AssertionError("No network name set.")
127
def initialize_on_transport(self, transport):
129
# hand off the request to the smart server
130
client_medium = transport.get_smart_medium()
131
except errors.NoSmartMedium:
132
# TODO: lookup the local format from a server hint.
133
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
134
return local_dir_format.initialize_on_transport(transport)
135
client = _SmartClient(client_medium)
136
path = client.remote_path_from_transport(transport)
138
response = client.call('BzrDirFormat.initialize', path)
139
except errors.ErrorFromSmartServer, err:
140
_translate_error(err, path=path)
141
if response[0] != 'ok':
142
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
143
format = RemoteBzrDirFormat()
144
self._supply_sub_formats_to(format)
145
return RemoteBzrDir(transport, format)
147
def parse_NoneTrueFalse(self, arg):
154
raise AssertionError("invalid arg %r" % arg)
156
def _serialize_NoneTrueFalse(self, arg):
163
def _serialize_NoneString(self, arg):
166
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
167
create_prefix=False, force_new_repo=False, stacked_on=None,
168
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
171
# hand off the request to the smart server
172
client_medium = transport.get_smart_medium()
173
except errors.NoSmartMedium:
176
# Decline to open it if the server doesn't support our required
177
# version (3) so that the VFS-based transport will do it.
178
if client_medium.should_probe():
180
server_version = client_medium.protocol_version()
181
if server_version != '2':
185
except errors.SmartProtocolError:
186
# Apparently there's no usable smart server there, even though
187
# the medium supports the smart protocol.
192
client = _SmartClient(client_medium)
193
path = client.remote_path_from_transport(transport)
194
if client_medium._is_remote_before((1, 16)):
197
# TODO: lookup the local format from a server hint.
198
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
199
self._supply_sub_formats_to(local_dir_format)
200
return local_dir_format.initialize_on_transport_ex(transport,
201
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
202
force_new_repo=force_new_repo, stacked_on=stacked_on,
203
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
204
make_working_trees=make_working_trees, shared_repo=shared_repo,
206
return self._initialize_on_transport_ex_rpc(client, path, transport,
207
use_existing_dir, create_prefix, force_new_repo, stacked_on,
208
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
210
def _initialize_on_transport_ex_rpc(self, client, path, transport,
211
use_existing_dir, create_prefix, force_new_repo, stacked_on,
212
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
214
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
215
args.append(self._serialize_NoneTrueFalse(create_prefix))
216
args.append(self._serialize_NoneTrueFalse(force_new_repo))
217
args.append(self._serialize_NoneString(stacked_on))
218
# stack_on_pwd is often/usually our transport
221
stack_on_pwd = transport.relpath(stack_on_pwd)
224
except errors.PathNotChild:
226
args.append(self._serialize_NoneString(stack_on_pwd))
227
args.append(self._serialize_NoneString(repo_format_name))
228
args.append(self._serialize_NoneTrueFalse(make_working_trees))
229
args.append(self._serialize_NoneTrueFalse(shared_repo))
230
request_network_name = self._network_name or \
231
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
233
response = client.call('BzrDirFormat.initialize_ex_1.16',
234
request_network_name, path, *args)
235
except errors.UnknownSmartMethod:
236
client._medium._remember_remote_is_before((1,16))
237
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
238
self._supply_sub_formats_to(local_dir_format)
239
return local_dir_format.initialize_on_transport_ex(transport,
240
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
241
force_new_repo=force_new_repo, stacked_on=stacked_on,
242
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
243
make_working_trees=make_working_trees, shared_repo=shared_repo,
245
except errors.ErrorFromSmartServer, err:
246
_translate_error(err, path=path)
247
repo_path = response[0]
248
bzrdir_name = response[6]
249
require_stacking = response[7]
250
require_stacking = self.parse_NoneTrueFalse(require_stacking)
251
format = RemoteBzrDirFormat()
252
format._network_name = bzrdir_name
253
self._supply_sub_formats_to(format)
254
bzrdir = RemoteBzrDir(transport, format, _client=client)
256
repo_format = response_tuple_to_repo_format(response[1:])
260
repo_bzrdir_format = RemoteBzrDirFormat()
261
repo_bzrdir_format._network_name = response[5]
262
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
266
final_stack = response[8] or None
267
final_stack_pwd = response[9] or None
269
final_stack_pwd = urlutils.join(
270
transport.base, final_stack_pwd)
271
remote_repo = RemoteRepository(repo_bzr, repo_format)
272
if len(response) > 10:
273
# Updated server verb that locks remotely.
274
repo_lock_token = response[10] or None
275
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
277
remote_repo.dont_leave_lock_in_place()
279
remote_repo.lock_write()
280
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
281
final_stack_pwd, require_stacking)
282
policy.acquire_repository()
286
bzrdir._format.set_branch_format(self.get_branch_format())
288
# The repo has already been created, but we need to make sure that
289
# we'll make a stackable branch.
290
bzrdir._format.require_stacking(_skip_repo=True)
291
return remote_repo, bzrdir, require_stacking, policy
293
def _open(self, transport):
294
return RemoteBzrDir(transport, self)
296
def __eq__(self, other):
297
if not isinstance(other, RemoteBzrDirFormat):
299
return self.get_format_description() == other.get_format_description()
301
def __return_repository_format(self):
302
# Always return a RemoteRepositoryFormat object, but if a specific bzr
303
# repository format has been asked for, tell the RemoteRepositoryFormat
304
# that it should use that for init() etc.
305
result = RemoteRepositoryFormat()
306
custom_format = getattr(self, '_repository_format', None)
308
if isinstance(custom_format, RemoteRepositoryFormat):
311
# We will use the custom format to create repositories over the
312
# wire; expose its details like rich_root_data for code to
314
result._custom_format = custom_format
317
def get_branch_format(self):
318
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
319
if not isinstance(result, RemoteBranchFormat):
320
new_result = RemoteBranchFormat()
321
new_result._custom_format = result
323
self.set_branch_format(new_result)
327
repository_format = property(__return_repository_format,
328
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
331
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
332
"""Control directory on a remote server, accessed via bzr:// or similar."""
334
def __init__(self, transport, format, _client=None, _force_probe=False):
335
"""Construct a RemoteBzrDir.
337
:param _client: Private parameter for testing. Disables probing and the
338
use of a real bzrdir.
340
_mod_bzrdir.BzrDir.__init__(self, transport, format)
341
# this object holds a delegated bzrdir that uses file-level operations
342
# to talk to the other side
343
self._real_bzrdir = None
344
self._has_working_tree = None
345
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
346
# create_branch for details.
347
self._next_open_branch_result = None
350
medium = transport.get_smart_medium()
351
self._client = client._SmartClient(medium)
353
self._client = _client
360
return '%s(%r)' % (self.__class__.__name__, self._client)
362
def _probe_bzrdir(self):
363
medium = self._client._medium
364
path = self._path_for_remote_call(self._client)
365
if medium._is_remote_before((2, 1)):
369
self._rpc_open_2_1(path)
371
except errors.UnknownSmartMethod:
372
medium._remember_remote_is_before((2, 1))
375
def _rpc_open_2_1(self, path):
376
response = self._call('BzrDir.open_2.1', path)
377
if response == ('no',):
378
raise errors.NotBranchError(path=self.root_transport.base)
379
elif response[0] == 'yes':
380
if response[1] == 'yes':
381
self._has_working_tree = True
382
elif response[1] == 'no':
383
self._has_working_tree = False
385
raise errors.UnexpectedSmartServerResponse(response)
387
raise errors.UnexpectedSmartServerResponse(response)
389
def _rpc_open(self, path):
390
response = self._call('BzrDir.open', path)
391
if response not in [('yes',), ('no',)]:
392
raise errors.UnexpectedSmartServerResponse(response)
393
if response == ('no',):
394
raise errors.NotBranchError(path=self.root_transport.base)
396
def _ensure_real(self):
397
"""Ensure that there is a _real_bzrdir set.
399
Used before calls to self._real_bzrdir.
401
if not self._real_bzrdir:
402
if 'hpssvfs' in debug.debug_flags:
404
warning('VFS BzrDir access triggered\n%s',
405
''.join(traceback.format_stack()))
406
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
407
self.root_transport, _server_formats=False)
408
self._format._network_name = \
409
self._real_bzrdir._format.network_name()
411
def _translate_error(self, err, **context):
412
_translate_error(err, bzrdir=self, **context)
414
def break_lock(self):
415
# Prevent aliasing problems in the next_open_branch_result cache.
416
# See create_branch for rationale.
417
self._next_open_branch_result = None
418
return _mod_bzrdir.BzrDir.break_lock(self)
420
def _vfs_cloning_metadir(self, require_stacking=False):
422
return self._real_bzrdir.cloning_metadir(
423
require_stacking=require_stacking)
425
def cloning_metadir(self, require_stacking=False):
426
medium = self._client._medium
427
if medium._is_remote_before((1, 13)):
428
return self._vfs_cloning_metadir(require_stacking=require_stacking)
429
verb = 'BzrDir.cloning_metadir'
434
path = self._path_for_remote_call(self._client)
436
response = self._call(verb, path, stacking)
437
except errors.UnknownSmartMethod:
438
medium._remember_remote_is_before((1, 13))
439
return self._vfs_cloning_metadir(require_stacking=require_stacking)
440
except errors.UnknownErrorFromSmartServer, err:
441
if err.error_tuple != ('BranchReference',):
443
# We need to resolve the branch reference to determine the
444
# cloning_metadir. This causes unnecessary RPCs to open the
445
# referenced branch (and bzrdir, etc) but only when the caller
446
# didn't already resolve the branch reference.
447
referenced_branch = self.open_branch()
448
return referenced_branch.bzrdir.cloning_metadir()
449
if len(response) != 3:
450
raise errors.UnexpectedSmartServerResponse(response)
451
control_name, repo_name, branch_info = response
452
if len(branch_info) != 2:
453
raise errors.UnexpectedSmartServerResponse(response)
454
branch_ref, branch_name = branch_info
455
format = controldir.network_format_registry.get(control_name)
457
format.repository_format = _mod_repository.network_format_registry.get(
459
if branch_ref == 'ref':
460
# XXX: we need possible_transports here to avoid reopening the
461
# connection to the referenced location
462
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
463
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
464
format.set_branch_format(branch_format)
465
elif branch_ref == 'branch':
467
format.set_branch_format(
468
branch.network_format_registry.get(branch_name))
470
raise errors.UnexpectedSmartServerResponse(response)
473
def create_repository(self, shared=False):
474
# as per meta1 formats - just delegate to the format object which may
476
result = self._format.repository_format.initialize(self, shared)
477
if not isinstance(result, RemoteRepository):
478
return self.open_repository()
482
def destroy_repository(self):
483
"""See BzrDir.destroy_repository"""
485
self._real_bzrdir.destroy_repository()
487
def create_branch(self, name=None, repository=None):
488
# as per meta1 formats - just delegate to the format object which may
490
real_branch = self._format.get_branch_format().initialize(self,
491
name=name, repository=repository)
492
if not isinstance(real_branch, RemoteBranch):
493
if not isinstance(repository, RemoteRepository):
494
raise AssertionError(
495
'need a RemoteRepository to use with RemoteBranch, got %r'
497
result = RemoteBranch(self, repository, real_branch, name=name)
500
# BzrDir.clone_on_transport() uses the result of create_branch but does
501
# not return it to its callers; we save approximately 8% of our round
502
# trips by handing the branch we created back to the first caller to
503
# open_branch rather than probing anew. Long term we need a API in
504
# bzrdir that doesn't discard result objects (like result_branch).
506
self._next_open_branch_result = result
509
def destroy_branch(self, name=None):
510
"""See BzrDir.destroy_branch"""
512
self._real_bzrdir.destroy_branch(name=name)
513
self._next_open_branch_result = None
515
def create_workingtree(self, revision_id=None, from_branch=None,
516
accelerator_tree=None, hardlink=False):
517
raise errors.NotLocalUrl(self.transport.base)
519
def find_branch_format(self, name=None):
520
"""Find the branch 'format' for this bzrdir.
522
This might be a synthetic object for e.g. RemoteBranch and SVN.
524
b = self.open_branch(name=name)
527
def get_branch_reference(self, name=None):
528
"""See BzrDir.get_branch_reference()."""
530
# XXX JRV20100304: Support opening colocated branches
531
raise errors.NoColocatedBranchSupport(self)
532
response = self._get_branch_reference()
533
if response[0] == 'ref':
538
def _get_branch_reference(self):
539
path = self._path_for_remote_call(self._client)
540
medium = self._client._medium
542
('BzrDir.open_branchV3', (2, 1)),
543
('BzrDir.open_branchV2', (1, 13)),
544
('BzrDir.open_branch', None),
546
for verb, required_version in candidate_calls:
547
if required_version and medium._is_remote_before(required_version):
550
response = self._call(verb, path)
551
except errors.UnknownSmartMethod:
552
if required_version is None:
554
medium._remember_remote_is_before(required_version)
557
if verb == 'BzrDir.open_branch':
558
if response[0] != 'ok':
559
raise errors.UnexpectedSmartServerResponse(response)
560
if response[1] != '':
561
return ('ref', response[1])
563
return ('branch', '')
564
if response[0] not in ('ref', 'branch'):
565
raise errors.UnexpectedSmartServerResponse(response)
568
def _get_tree_branch(self, name=None):
569
"""See BzrDir._get_tree_branch()."""
570
return None, self.open_branch(name=name)
572
def open_branch(self, name=None, unsupported=False,
573
ignore_fallbacks=False):
575
raise NotImplementedError('unsupported flag support not implemented yet.')
576
if self._next_open_branch_result is not None:
577
# See create_branch for details.
578
result = self._next_open_branch_result
579
self._next_open_branch_result = None
581
response = self._get_branch_reference()
582
if response[0] == 'ref':
583
# a branch reference, use the existing BranchReference logic.
584
format = BranchReferenceFormat()
585
return format.open(self, name=name, _found=True,
586
location=response[1], ignore_fallbacks=ignore_fallbacks)
587
branch_format_name = response[1]
588
if not branch_format_name:
589
branch_format_name = None
590
format = RemoteBranchFormat(network_name=branch_format_name)
591
return RemoteBranch(self, self.find_repository(), format=format,
592
setup_stacking=not ignore_fallbacks, name=name)
594
def _open_repo_v1(self, path):
595
verb = 'BzrDir.find_repository'
596
response = self._call(verb, path)
597
if response[0] != 'ok':
598
raise errors.UnexpectedSmartServerResponse(response)
599
# servers that only support the v1 method don't support external
602
repo = self._real_bzrdir.open_repository()
603
response = response + ('no', repo._format.network_name())
604
return response, repo
606
def _open_repo_v2(self, path):
607
verb = 'BzrDir.find_repositoryV2'
608
response = self._call(verb, path)
609
if response[0] != 'ok':
610
raise errors.UnexpectedSmartServerResponse(response)
612
repo = self._real_bzrdir.open_repository()
613
response = response + (repo._format.network_name(),)
614
return response, repo
616
def _open_repo_v3(self, path):
617
verb = 'BzrDir.find_repositoryV3'
618
medium = self._client._medium
619
if medium._is_remote_before((1, 13)):
620
raise errors.UnknownSmartMethod(verb)
622
response = self._call(verb, path)
623
except errors.UnknownSmartMethod:
624
medium._remember_remote_is_before((1, 13))
626
if response[0] != 'ok':
627
raise errors.UnexpectedSmartServerResponse(response)
628
return response, None
630
def open_repository(self):
631
path = self._path_for_remote_call(self._client)
633
for probe in [self._open_repo_v3, self._open_repo_v2,
636
response, real_repo = probe(path)
638
except errors.UnknownSmartMethod:
641
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
642
if response[0] != 'ok':
643
raise errors.UnexpectedSmartServerResponse(response)
644
if len(response) != 6:
645
raise SmartProtocolError('incorrect response length %s' % (response,))
646
if response[1] == '':
647
# repo is at this dir.
648
format = response_tuple_to_repo_format(response[2:])
649
# Used to support creating a real format instance when needed.
650
format._creating_bzrdir = self
651
remote_repo = RemoteRepository(self, format)
652
format._creating_repo = remote_repo
653
if real_repo is not None:
654
remote_repo._set_real_repository(real_repo)
657
raise errors.NoRepositoryPresent(self)
659
def has_workingtree(self):
660
if self._has_working_tree is None:
662
self._has_working_tree = self._real_bzrdir.has_workingtree()
663
return self._has_working_tree
665
def open_workingtree(self, recommend_upgrade=True):
666
if self.has_workingtree():
667
raise errors.NotLocalUrl(self.root_transport)
669
raise errors.NoWorkingTree(self.root_transport.base)
671
def _path_for_remote_call(self, client):
672
"""Return the path to be used for this bzrdir in a remote call."""
673
return client.remote_path_from_transport(self.root_transport)
675
def get_branch_transport(self, branch_format, name=None):
677
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
679
def get_repository_transport(self, repository_format):
681
return self._real_bzrdir.get_repository_transport(repository_format)
683
def get_workingtree_transport(self, workingtree_format):
685
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
687
def can_convert_format(self):
688
"""Upgrading of remote bzrdirs is not supported yet."""
691
def needs_format_conversion(self, format):
692
"""Upgrading of remote bzrdirs is not supported yet."""
695
def clone(self, url, revision_id=None, force_new_repo=False,
696
preserve_stacking=False):
698
return self._real_bzrdir.clone(url, revision_id=revision_id,
699
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
701
def _get_config(self):
702
return RemoteBzrDirConfig(self)
705
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
706
"""Format for repositories accessed over a _SmartClient.
708
Instances of this repository are represented by RemoteRepository
711
The RemoteRepositoryFormat is parameterized during construction
712
to reflect the capabilities of the real, remote format. Specifically
713
the attributes rich_root_data and supports_tree_reference are set
714
on a per instance basis, and are not set (and should not be) at
717
:ivar _custom_format: If set, a specific concrete repository format that
718
will be used when initializing a repository with this
719
RemoteRepositoryFormat.
720
:ivar _creating_repo: If set, the repository object that this
721
RemoteRepositoryFormat was created for: it can be called into
722
to obtain data like the network name.
725
_matchingbzrdir = RemoteBzrDirFormat()
726
supports_full_versioned_files = True
727
supports_leaving_lock = True
730
_mod_repository.RepositoryFormat.__init__(self)
731
self._custom_format = None
732
self._network_name = None
733
self._creating_bzrdir = None
734
self._revision_graph_can_have_wrong_parents = None
735
self._supports_chks = None
736
self._supports_external_lookups = None
737
self._supports_tree_reference = None
738
self._supports_funky_characters = None
739
self._rich_root_data = None
742
return "%s(_network_name=%r)" % (self.__class__.__name__,
746
def fast_deltas(self):
748
return self._custom_format.fast_deltas
751
def rich_root_data(self):
752
if self._rich_root_data is None:
754
self._rich_root_data = self._custom_format.rich_root_data
755
return self._rich_root_data
758
def supports_chks(self):
759
if self._supports_chks is None:
761
self._supports_chks = self._custom_format.supports_chks
762
return self._supports_chks
765
def supports_external_lookups(self):
766
if self._supports_external_lookups is None:
768
self._supports_external_lookups = \
769
self._custom_format.supports_external_lookups
770
return self._supports_external_lookups
773
def supports_funky_characters(self):
774
if self._supports_funky_characters is None:
776
self._supports_funky_characters = \
777
self._custom_format.supports_funky_characters
778
return self._supports_funky_characters
781
def supports_tree_reference(self):
782
if self._supports_tree_reference is None:
784
self._supports_tree_reference = \
785
self._custom_format.supports_tree_reference
786
return self._supports_tree_reference
789
def revision_graph_can_have_wrong_parents(self):
790
if self._revision_graph_can_have_wrong_parents is None:
792
self._revision_graph_can_have_wrong_parents = \
793
self._custom_format.revision_graph_can_have_wrong_parents
794
return self._revision_graph_can_have_wrong_parents
796
def _vfs_initialize(self, a_bzrdir, shared):
797
"""Helper for common code in initialize."""
798
if self._custom_format:
799
# Custom format requested
800
result = self._custom_format.initialize(a_bzrdir, shared=shared)
801
elif self._creating_bzrdir is not None:
802
# Use the format that the repository we were created to back
804
prior_repo = self._creating_bzrdir.open_repository()
805
prior_repo._ensure_real()
806
result = prior_repo._real_repository._format.initialize(
807
a_bzrdir, shared=shared)
809
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
810
# support remote initialization.
811
# We delegate to a real object at this point (as RemoteBzrDir
812
# delegate to the repository format which would lead to infinite
813
# recursion if we just called a_bzrdir.create_repository.
814
a_bzrdir._ensure_real()
815
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
816
if not isinstance(result, RemoteRepository):
817
return self.open(a_bzrdir)
821
def initialize(self, a_bzrdir, shared=False):
822
# Being asked to create on a non RemoteBzrDir:
823
if not isinstance(a_bzrdir, RemoteBzrDir):
824
return self._vfs_initialize(a_bzrdir, shared)
825
medium = a_bzrdir._client._medium
826
if medium._is_remote_before((1, 13)):
827
return self._vfs_initialize(a_bzrdir, shared)
828
# Creating on a remote bzr dir.
829
# 1) get the network name to use.
830
if self._custom_format:
831
network_name = self._custom_format.network_name()
832
elif self._network_name:
833
network_name = self._network_name
835
# Select the current bzrlib default and ask for that.
836
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
837
reference_format = reference_bzrdir_format.repository_format
838
network_name = reference_format.network_name()
839
# 2) try direct creation via RPC
840
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
841
verb = 'BzrDir.create_repository'
847
response = a_bzrdir._call(verb, path, network_name, shared_str)
848
except errors.UnknownSmartMethod:
849
# Fallback - use vfs methods
850
medium._remember_remote_is_before((1, 13))
851
return self._vfs_initialize(a_bzrdir, shared)
853
# Turn the response into a RemoteRepository object.
854
format = response_tuple_to_repo_format(response[1:])
855
# Used to support creating a real format instance when needed.
856
format._creating_bzrdir = a_bzrdir
857
remote_repo = RemoteRepository(a_bzrdir, format)
858
format._creating_repo = remote_repo
861
def open(self, a_bzrdir):
862
if not isinstance(a_bzrdir, RemoteBzrDir):
863
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
864
return a_bzrdir.open_repository()
866
def _ensure_real(self):
867
if self._custom_format is None:
868
self._custom_format = _mod_repository.network_format_registry.get(
872
def _fetch_order(self):
874
return self._custom_format._fetch_order
877
def _fetch_uses_deltas(self):
879
return self._custom_format._fetch_uses_deltas
882
def _fetch_reconcile(self):
884
return self._custom_format._fetch_reconcile
886
def get_format_description(self):
888
return 'Remote: ' + self._custom_format.get_format_description()
890
def __eq__(self, other):
891
return self.__class__ is other.__class__
893
def network_name(self):
894
if self._network_name:
895
return self._network_name
896
self._creating_repo._ensure_real()
897
return self._creating_repo._real_repository._format.network_name()
900
def pack_compresses(self):
902
return self._custom_format.pack_compresses
905
def _serializer(self):
907
return self._custom_format._serializer
910
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
911
controldir.ControlComponent):
912
"""Repository accessed over rpc.
914
For the moment most operations are performed using local transport-backed
918
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
919
"""Create a RemoteRepository instance.
921
:param remote_bzrdir: The bzrdir hosting this repository.
922
:param format: The RemoteFormat object to use.
923
:param real_repository: If not None, a local implementation of the
924
repository logic for the repository, usually accessing the data
926
:param _client: Private testing parameter - override the smart client
927
to be used by the repository.
930
self._real_repository = real_repository
932
self._real_repository = None
933
self.bzrdir = remote_bzrdir
935
self._client = remote_bzrdir._client
937
self._client = _client
938
self._format = format
939
self._lock_mode = None
940
self._lock_token = None
942
self._leave_lock = False
943
# Cache of revision parents; misses are cached during read locks, and
944
# write locks when no _real_repository has been set.
945
self._unstacked_provider = graph.CachingParentsProvider(
946
get_parent_map=self._get_parent_map_rpc)
947
self._unstacked_provider.disable_cache()
949
# These depend on the actual remote format, so force them off for
950
# maximum compatibility. XXX: In future these should depend on the
951
# remote repository instance, but this is irrelevant until we perform
952
# reconcile via an RPC call.
953
self._reconcile_does_inventory_gc = False
954
self._reconcile_fixes_text_parents = False
955
self._reconcile_backsup_inventory = False
956
self.base = self.bzrdir.transport.base
957
# Additional places to query for data.
958
self._fallback_repositories = []
961
def user_transport(self):
962
return self.bzrdir.user_transport
965
def control_transport(self):
966
# XXX: Normally you shouldn't directly get at the remote repository
967
# transport, but I'm not sure it's worth making this method
968
# optional -- mbp 2010-04-21
969
return self.bzrdir.get_repository_transport(None)
972
return "%s(%s)" % (self.__class__.__name__, self.base)
976
def abort_write_group(self, suppress_errors=False):
977
"""Complete a write group on the decorated repository.
979
Smart methods perform operations in a single step so this API
980
is not really applicable except as a compatibility thunk
981
for older plugins that don't use e.g. the CommitBuilder
984
:param suppress_errors: see Repository.abort_write_group.
987
return self._real_repository.abort_write_group(
988
suppress_errors=suppress_errors)
992
"""Decorate the real repository for now.
994
In the long term a full blown network facility is needed to avoid
995
creating a real repository object locally.
998
return self._real_repository.chk_bytes
1000
def commit_write_group(self):
1001
"""Complete a write group on the decorated repository.
1003
Smart methods perform operations in a single step so this API
1004
is not really applicable except as a compatibility thunk
1005
for older plugins that don't use e.g. the CommitBuilder
1009
return self._real_repository.commit_write_group()
1011
def resume_write_group(self, tokens):
1013
return self._real_repository.resume_write_group(tokens)
1015
def suspend_write_group(self):
1017
return self._real_repository.suspend_write_group()
1019
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1021
return self._real_repository.get_missing_parent_inventories(
1022
check_for_missing_texts=check_for_missing_texts)
1024
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1026
return self._real_repository.get_rev_id_for_revno(
1029
def get_rev_id_for_revno(self, revno, known_pair):
1030
"""See Repository.get_rev_id_for_revno."""
1031
path = self.bzrdir._path_for_remote_call(self._client)
1033
if self._client._medium._is_remote_before((1, 17)):
1034
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1035
response = self._call(
1036
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1037
except errors.UnknownSmartMethod:
1038
self._client._medium._remember_remote_is_before((1, 17))
1039
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1040
if response[0] == 'ok':
1041
return True, response[1]
1042
elif response[0] == 'history-incomplete':
1043
known_pair = response[1:3]
1044
for fallback in self._fallback_repositories:
1045
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1050
# Not found in any fallbacks
1051
return False, known_pair
1053
raise errors.UnexpectedSmartServerResponse(response)
1055
def _ensure_real(self):
1056
"""Ensure that there is a _real_repository set.
1058
Used before calls to self._real_repository.
1060
Note that _ensure_real causes many roundtrips to the server which are
1061
not desirable, and prevents the use of smart one-roundtrip RPC's to
1062
perform complex operations (such as accessing parent data, streaming
1063
revisions etc). Adding calls to _ensure_real should only be done when
1064
bringing up new functionality, adding fallbacks for smart methods that
1065
require a fallback path, and never to replace an existing smart method
1066
invocation. If in doubt chat to the bzr network team.
1068
if self._real_repository is None:
1069
if 'hpssvfs' in debug.debug_flags:
1071
warning('VFS Repository access triggered\n%s',
1072
''.join(traceback.format_stack()))
1073
self._unstacked_provider.missing_keys.clear()
1074
self.bzrdir._ensure_real()
1075
self._set_real_repository(
1076
self.bzrdir._real_bzrdir.open_repository())
1078
def _translate_error(self, err, **context):
1079
self.bzrdir._translate_error(err, repository=self, **context)
1081
def find_text_key_references(self):
1082
"""Find the text key references within the repository.
1084
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1085
to whether they were referred to by the inventory of the
1086
revision_id that they contain. The inventory texts from all present
1087
revision ids are assessed to generate this report.
1090
return self._real_repository.find_text_key_references()
1092
def _generate_text_key_index(self):
1093
"""Generate a new text key index for the repository.
1095
This is an expensive function that will take considerable time to run.
1097
:return: A dict mapping (file_id, revision_id) tuples to a list of
1098
parents, also (file_id, revision_id) tuples.
1101
return self._real_repository._generate_text_key_index()
1103
def _get_revision_graph(self, revision_id):
1104
"""Private method for using with old (< 1.2) servers to fallback."""
1105
if revision_id is None:
1107
elif _mod_revision.is_null(revision_id):
1110
path = self.bzrdir._path_for_remote_call(self._client)
1111
response = self._call_expecting_body(
1112
'Repository.get_revision_graph', path, revision_id)
1113
response_tuple, response_handler = response
1114
if response_tuple[0] != 'ok':
1115
raise errors.UnexpectedSmartServerResponse(response_tuple)
1116
coded = response_handler.read_body_bytes()
1118
# no revisions in this repository!
1120
lines = coded.split('\n')
1123
d = tuple(line.split())
1124
revision_graph[d[0]] = d[1:]
1126
return revision_graph
1128
def _get_sink(self):
1129
"""See Repository._get_sink()."""
1130
return RemoteStreamSink(self)
1132
def _get_source(self, to_format):
1133
"""Return a source for streaming from this repository."""
1134
return RemoteStreamSource(self, to_format)
1137
def get_file_graph(self):
1138
return graph.Graph(self.texts)
1141
def has_revision(self, revision_id):
1142
"""True if this repository has a copy of the revision."""
1143
# Copy of bzrlib.repository.Repository.has_revision
1144
return revision_id in self.has_revisions((revision_id,))
1147
def has_revisions(self, revision_ids):
1148
"""Probe to find out the presence of multiple revisions.
1150
:param revision_ids: An iterable of revision_ids.
1151
:return: A set of the revision_ids that were present.
1153
# Copy of bzrlib.repository.Repository.has_revisions
1154
parent_map = self.get_parent_map(revision_ids)
1155
result = set(parent_map)
1156
if _mod_revision.NULL_REVISION in revision_ids:
1157
result.add(_mod_revision.NULL_REVISION)
1160
def _has_same_fallbacks(self, other_repo):
1161
"""Returns true if the repositories have the same fallbacks."""
1162
# XXX: copied from Repository; it should be unified into a base class
1163
# <https://bugs.launchpad.net/bzr/+bug/401622>
1164
my_fb = self._fallback_repositories
1165
other_fb = other_repo._fallback_repositories
1166
if len(my_fb) != len(other_fb):
1168
for f, g in zip(my_fb, other_fb):
1169
if not f.has_same_location(g):
1173
def has_same_location(self, other):
1174
# TODO: Move to RepositoryBase and unify with the regular Repository
1175
# one; unfortunately the tests rely on slightly different behaviour at
1176
# present -- mbp 20090710
1177
return (self.__class__ is other.__class__ and
1178
self.bzrdir.transport.base == other.bzrdir.transport.base)
1180
def get_graph(self, other_repository=None):
1181
"""Return the graph for this repository format"""
1182
parents_provider = self._make_parents_provider(other_repository)
1183
return graph.Graph(parents_provider)
1186
def get_known_graph_ancestry(self, revision_ids):
1187
"""Return the known graph for a set of revision ids and their ancestors.
1189
st = static_tuple.StaticTuple
1190
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1191
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1192
return graph.GraphThunkIdsToKeys(known_graph)
1194
def gather_stats(self, revid=None, committers=None):
1195
"""See Repository.gather_stats()."""
1196
path = self.bzrdir._path_for_remote_call(self._client)
1197
# revid can be None to indicate no revisions, not just NULL_REVISION
1198
if revid is None or _mod_revision.is_null(revid):
1202
if committers is None or not committers:
1203
fmt_committers = 'no'
1205
fmt_committers = 'yes'
1206
response_tuple, response_handler = self._call_expecting_body(
1207
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1208
if response_tuple[0] != 'ok':
1209
raise errors.UnexpectedSmartServerResponse(response_tuple)
1211
body = response_handler.read_body_bytes()
1213
for line in body.split('\n'):
1216
key, val_text = line.split(':')
1217
if key in ('revisions', 'size', 'committers'):
1218
result[key] = int(val_text)
1219
elif key in ('firstrev', 'latestrev'):
1220
values = val_text.split(' ')[1:]
1221
result[key] = (float(values[0]), long(values[1]))
1225
def find_branches(self, using=False):
1226
"""See Repository.find_branches()."""
1227
# should be an API call to the server.
1229
return self._real_repository.find_branches(using=using)
1231
def get_physical_lock_status(self):
1232
"""See Repository.get_physical_lock_status()."""
1233
# should be an API call to the server.
1235
return self._real_repository.get_physical_lock_status()
1237
def is_in_write_group(self):
1238
"""Return True if there is an open write group.
1240
write groups are only applicable locally for the smart server..
1242
if self._real_repository:
1243
return self._real_repository.is_in_write_group()
1245
def is_locked(self):
1246
return self._lock_count >= 1
1248
def is_shared(self):
1249
"""See Repository.is_shared()."""
1250
path = self.bzrdir._path_for_remote_call(self._client)
1251
response = self._call('Repository.is_shared', path)
1252
if response[0] not in ('yes', 'no'):
1253
raise SmartProtocolError('unexpected response code %s' % (response,))
1254
return response[0] == 'yes'
1256
def is_write_locked(self):
1257
return self._lock_mode == 'w'
1259
def _warn_if_deprecated(self, branch=None):
1260
# If we have a real repository, the check will be done there, if we
1261
# don't the check will be done remotely.
1264
def lock_read(self):
1265
"""Lock the repository for read operations.
1267
:return: A bzrlib.lock.LogicalLockResult.
1269
# wrong eventually - want a local lock cache context
1270
if not self._lock_mode:
1271
self._note_lock('r')
1272
self._lock_mode = 'r'
1273
self._lock_count = 1
1274
self._unstacked_provider.enable_cache(cache_misses=True)
1275
if self._real_repository is not None:
1276
self._real_repository.lock_read()
1277
for repo in self._fallback_repositories:
1280
self._lock_count += 1
1281
return lock.LogicalLockResult(self.unlock)
1283
def _remote_lock_write(self, token):
1284
path = self.bzrdir._path_for_remote_call(self._client)
1287
err_context = {'token': token}
1288
response = self._call('Repository.lock_write', path, token,
1290
if response[0] == 'ok':
1291
ok, token = response
1294
raise errors.UnexpectedSmartServerResponse(response)
1296
def lock_write(self, token=None, _skip_rpc=False):
1297
if not self._lock_mode:
1298
self._note_lock('w')
1300
if self._lock_token is not None:
1301
if token != self._lock_token:
1302
raise errors.TokenMismatch(token, self._lock_token)
1303
self._lock_token = token
1305
self._lock_token = self._remote_lock_write(token)
1306
# if self._lock_token is None, then this is something like packs or
1307
# svn where we don't get to lock the repo, or a weave style repository
1308
# where we cannot lock it over the wire and attempts to do so will
1310
if self._real_repository is not None:
1311
self._real_repository.lock_write(token=self._lock_token)
1312
if token is not None:
1313
self._leave_lock = True
1315
self._leave_lock = False
1316
self._lock_mode = 'w'
1317
self._lock_count = 1
1318
cache_misses = self._real_repository is None
1319
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1320
for repo in self._fallback_repositories:
1321
# Writes don't affect fallback repos
1323
elif self._lock_mode == 'r':
1324
raise errors.ReadOnlyError(self)
1326
self._lock_count += 1
1327
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1329
def leave_lock_in_place(self):
1330
if not self._lock_token:
1331
raise NotImplementedError(self.leave_lock_in_place)
1332
self._leave_lock = True
1334
def dont_leave_lock_in_place(self):
1335
if not self._lock_token:
1336
raise NotImplementedError(self.dont_leave_lock_in_place)
1337
self._leave_lock = False
1339
def _set_real_repository(self, repository):
1340
"""Set the _real_repository for this repository.
1342
:param repository: The repository to fallback to for non-hpss
1343
implemented operations.
1345
if self._real_repository is not None:
1346
# Replacing an already set real repository.
1347
# We cannot do this [currently] if the repository is locked -
1348
# synchronised state might be lost.
1349
if self.is_locked():
1350
raise AssertionError('_real_repository is already set')
1351
if isinstance(repository, RemoteRepository):
1352
raise AssertionError()
1353
self._real_repository = repository
1354
# three code paths happen here:
1355
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1356
# up stacking. In this case self._fallback_repositories is [], and the
1357
# real repo is already setup. Preserve the real repo and
1358
# RemoteRepository.add_fallback_repository will avoid adding
1360
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1361
# ensure_real is triggered from a branch, the real repository to
1362
# set already has a matching list with separate instances, but
1363
# as they are also RemoteRepositories we don't worry about making the
1364
# lists be identical.
1365
# 3) new servers, RemoteRepository.ensure_real is triggered before
1366
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1367
# and need to populate it.
1368
if (self._fallback_repositories and
1369
len(self._real_repository._fallback_repositories) !=
1370
len(self._fallback_repositories)):
1371
if len(self._real_repository._fallback_repositories):
1372
raise AssertionError(
1373
"cannot cleanly remove existing _fallback_repositories")
1374
for fb in self._fallback_repositories:
1375
self._real_repository.add_fallback_repository(fb)
1376
if self._lock_mode == 'w':
1377
# if we are already locked, the real repository must be able to
1378
# acquire the lock with our token.
1379
self._real_repository.lock_write(self._lock_token)
1380
elif self._lock_mode == 'r':
1381
self._real_repository.lock_read()
1383
def start_write_group(self):
1384
"""Start a write group on the decorated repository.
1386
Smart methods perform operations in a single step so this API
1387
is not really applicable except as a compatibility thunk
1388
for older plugins that don't use e.g. the CommitBuilder
1392
return self._real_repository.start_write_group()
1394
def _unlock(self, token):
1395
path = self.bzrdir._path_for_remote_call(self._client)
1397
# with no token the remote repository is not persistently locked.
1399
err_context = {'token': token}
1400
response = self._call('Repository.unlock', path, token,
1402
if response == ('ok',):
1405
raise errors.UnexpectedSmartServerResponse(response)
1407
@only_raises(errors.LockNotHeld, errors.LockBroken)
1409
if not self._lock_count:
1410
return lock.cant_unlock_not_held(self)
1411
self._lock_count -= 1
1412
if self._lock_count > 0:
1414
self._unstacked_provider.disable_cache()
1415
old_mode = self._lock_mode
1416
self._lock_mode = None
1418
# The real repository is responsible at present for raising an
1419
# exception if it's in an unfinished write group. However, it
1420
# normally will *not* actually remove the lock from disk - that's
1421
# done by the server on receiving the Repository.unlock call.
1422
# This is just to let the _real_repository stay up to date.
1423
if self._real_repository is not None:
1424
self._real_repository.unlock()
1426
# The rpc-level lock should be released even if there was a
1427
# problem releasing the vfs-based lock.
1429
# Only write-locked repositories need to make a remote method
1430
# call to perform the unlock.
1431
old_token = self._lock_token
1432
self._lock_token = None
1433
if not self._leave_lock:
1434
self._unlock(old_token)
1435
# Fallbacks are always 'lock_read()' so we don't pay attention to
1437
for repo in self._fallback_repositories:
1440
def break_lock(self):
1441
# should hand off to the network
1443
return self._real_repository.break_lock()
1445
def _get_tarball(self, compression):
1446
"""Return a TemporaryFile containing a repository tarball.
1448
Returns None if the server does not support sending tarballs.
1451
path = self.bzrdir._path_for_remote_call(self._client)
1453
response, protocol = self._call_expecting_body(
1454
'Repository.tarball', path, compression)
1455
except errors.UnknownSmartMethod:
1456
protocol.cancel_read_body()
1458
if response[0] == 'ok':
1459
# Extract the tarball and return it
1460
t = tempfile.NamedTemporaryFile()
1461
# TODO: rpc layer should read directly into it...
1462
t.write(protocol.read_body_bytes())
1465
raise errors.UnexpectedSmartServerResponse(response)
1467
def sprout(self, to_bzrdir, revision_id=None):
1468
# TODO: Option to control what format is created?
1470
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1472
dest_repo.fetch(self, revision_id=revision_id)
1475
### These methods are just thin shims to the VFS object for now.
1477
def revision_tree(self, revision_id):
1479
return self._real_repository.revision_tree(revision_id)
1481
def get_serializer_format(self):
1483
return self._real_repository.get_serializer_format()
1485
def get_commit_builder(self, branch, parents, config, timestamp=None,
1486
timezone=None, committer=None, revprops=None,
1487
revision_id=None, lossy=False):
1488
# FIXME: It ought to be possible to call this without immediately
1489
# triggering _ensure_real. For now it's the easiest thing to do.
1491
real_repo = self._real_repository
1492
builder = real_repo.get_commit_builder(branch, parents,
1493
config, timestamp=timestamp, timezone=timezone,
1494
committer=committer, revprops=revprops,
1495
revision_id=revision_id, lossy=lossy)
1498
def add_fallback_repository(self, repository):
1499
"""Add a repository to use for looking up data not held locally.
1501
:param repository: A repository.
1503
if not self._format.supports_external_lookups:
1504
raise errors.UnstackableRepositoryFormat(
1505
self._format.network_name(), self.base)
1506
# We need to accumulate additional repositories here, to pass them in
1509
if self.is_locked():
1510
# We will call fallback.unlock() when we transition to the unlocked
1511
# state, so always add a lock here. If a caller passes us a locked
1512
# repository, they are responsible for unlocking it later.
1513
repository.lock_read()
1514
self._check_fallback_repository(repository)
1515
self._fallback_repositories.append(repository)
1516
# If self._real_repository was parameterised already (e.g. because a
1517
# _real_branch had its get_stacked_on_url method called), then the
1518
# repository to be added may already be in the _real_repositories list.
1519
if self._real_repository is not None:
1520
fallback_locations = [repo.user_url for repo in
1521
self._real_repository._fallback_repositories]
1522
if repository.user_url not in fallback_locations:
1523
self._real_repository.add_fallback_repository(repository)
1525
def _check_fallback_repository(self, repository):
1526
"""Check that this repository can fallback to repository safely.
1528
Raise an error if not.
1530
:param repository: A repository to fallback to.
1532
return _mod_repository.InterRepository._assert_same_model(
1535
def add_inventory(self, revid, inv, parents):
1537
return self._real_repository.add_inventory(revid, inv, parents)
1539
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1540
parents, basis_inv=None, propagate_caches=False):
1542
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1543
delta, new_revision_id, parents, basis_inv=basis_inv,
1544
propagate_caches=propagate_caches)
1546
def add_revision(self, rev_id, rev, inv=None, config=None):
1548
return self._real_repository.add_revision(
1549
rev_id, rev, inv=inv, config=config)
1552
def get_inventory(self, revision_id):
1554
return self._real_repository.get_inventory(revision_id)
1556
def iter_inventories(self, revision_ids, ordering=None):
1558
return self._real_repository.iter_inventories(revision_ids, ordering)
1561
def get_revision(self, revision_id):
1563
return self._real_repository.get_revision(revision_id)
1565
def get_transaction(self):
1567
return self._real_repository.get_transaction()
1570
def clone(self, a_bzrdir, revision_id=None):
1572
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1574
def make_working_trees(self):
1575
"""See Repository.make_working_trees"""
1577
return self._real_repository.make_working_trees()
1579
def refresh_data(self):
1580
"""Re-read any data needed to synchronise with disk.
1582
This method is intended to be called after another repository instance
1583
(such as one used by a smart server) has inserted data into the
1584
repository. On all repositories this will work outside of write groups.
1585
Some repository formats (pack and newer for bzrlib native formats)
1586
support refresh_data inside write groups. If called inside a write
1587
group on a repository that does not support refreshing in a write group
1588
IsInWriteGroupError will be raised.
1590
if self._real_repository is not None:
1591
self._real_repository.refresh_data()
1593
def revision_ids_to_search_result(self, result_set):
1594
"""Convert a set of revision ids to a graph SearchResult."""
1595
result_parents = set()
1596
for parents in self.get_graph().get_parent_map(
1597
result_set).itervalues():
1598
result_parents.update(parents)
1599
included_keys = result_set.intersection(result_parents)
1600
start_keys = result_set.difference(included_keys)
1601
exclude_keys = result_parents.difference(result_set)
1602
result = graph.SearchResult(start_keys, exclude_keys,
1603
len(result_set), result_set)
1607
def search_missing_revision_ids(self, other,
1608
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1609
find_ghosts=True, revision_ids=None, if_present_ids=None,
1611
"""Return the revision ids that other has that this does not.
1613
These are returned in topological order.
1615
revision_id: only return revision ids included by revision_id.
1617
if symbol_versioning.deprecated_passed(revision_id):
1618
symbol_versioning.warn(
1619
'search_missing_revision_ids(revision_id=...) was '
1620
'deprecated in 2.4. Use revision_ids=[...] instead.',
1621
DeprecationWarning, stacklevel=2)
1622
if revision_ids is not None:
1623
raise AssertionError(
1624
'revision_ids is mutually exclusive with revision_id')
1625
if revision_id is not None:
1626
revision_ids = [revision_id]
1627
inter_repo = _mod_repository.InterRepository.get(other, self)
1628
return inter_repo.search_missing_revision_ids(
1629
find_ghosts=find_ghosts, revision_ids=revision_ids,
1630
if_present_ids=if_present_ids, limit=limit)
1632
def fetch(self, source, revision_id=None, find_ghosts=False,
1634
# No base implementation to use as RemoteRepository is not a subclass
1635
# of Repository; so this is a copy of Repository.fetch().
1636
if fetch_spec is not None and revision_id is not None:
1637
raise AssertionError(
1638
"fetch_spec and revision_id are mutually exclusive.")
1639
if self.is_in_write_group():
1640
raise errors.InternalBzrError(
1641
"May not fetch while in a write group.")
1642
# fast path same-url fetch operations
1643
if (self.has_same_location(source)
1644
and fetch_spec is None
1645
and self._has_same_fallbacks(source)):
1646
# check that last_revision is in 'from' and then return a
1648
if (revision_id is not None and
1649
not _mod_revision.is_null(revision_id)):
1650
self.get_revision(revision_id)
1652
# if there is no specific appropriate InterRepository, this will get
1653
# the InterRepository base class, which raises an
1654
# IncompatibleRepositories when asked to fetch.
1655
inter = _mod_repository.InterRepository.get(source, self)
1656
return inter.fetch(revision_id=revision_id,
1657
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1659
def create_bundle(self, target, base, fileobj, format=None):
1661
self._real_repository.create_bundle(target, base, fileobj, format)
1664
def get_ancestry(self, revision_id, topo_sorted=True):
1666
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1668
def fileids_altered_by_revision_ids(self, revision_ids):
1670
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1672
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1674
return self._real_repository._get_versioned_file_checker(
1675
revisions, revision_versions_cache)
1677
def iter_files_bytes(self, desired_files):
1678
"""See Repository.iter_file_bytes.
1681
return self._real_repository.iter_files_bytes(desired_files)
1683
def get_parent_map(self, revision_ids):
1684
"""See bzrlib.Graph.get_parent_map()."""
1685
return self._make_parents_provider().get_parent_map(revision_ids)
1687
def _get_parent_map_rpc(self, keys):
1688
"""Helper for get_parent_map that performs the RPC."""
1689
medium = self._client._medium
1690
if medium._is_remote_before((1, 2)):
1691
# We already found out that the server can't understand
1692
# Repository.get_parent_map requests, so just fetch the whole
1695
# Note that this reads the whole graph, when only some keys are
1696
# wanted. On this old server there's no way (?) to get them all
1697
# in one go, and the user probably will have seen a warning about
1698
# the server being old anyhow.
1699
rg = self._get_revision_graph(None)
1700
# There is an API discrepancy between get_parent_map and
1701
# get_revision_graph. Specifically, a "key:()" pair in
1702
# get_revision_graph just means a node has no parents. For
1703
# "get_parent_map" it means the node is a ghost. So fix up the
1704
# graph to correct this.
1705
# https://bugs.launchpad.net/bzr/+bug/214894
1706
# There is one other "bug" which is that ghosts in
1707
# get_revision_graph() are not returned at all. But we won't worry
1708
# about that for now.
1709
for node_id, parent_ids in rg.iteritems():
1710
if parent_ids == ():
1711
rg[node_id] = (NULL_REVISION,)
1712
rg[NULL_REVISION] = ()
1717
raise ValueError('get_parent_map(None) is not valid')
1718
if NULL_REVISION in keys:
1719
keys.discard(NULL_REVISION)
1720
found_parents = {NULL_REVISION:()}
1722
return found_parents
1725
# TODO(Needs analysis): We could assume that the keys being requested
1726
# from get_parent_map are in a breadth first search, so typically they
1727
# will all be depth N from some common parent, and we don't have to
1728
# have the server iterate from the root parent, but rather from the
1729
# keys we're searching; and just tell the server the keyspace we
1730
# already have; but this may be more traffic again.
1732
# Transform self._parents_map into a search request recipe.
1733
# TODO: Manage this incrementally to avoid covering the same path
1734
# repeatedly. (The server will have to on each request, but the less
1735
# work done the better).
1737
# Negative caching notes:
1738
# new server sends missing when a request including the revid
1739
# 'include-missing:' is present in the request.
1740
# missing keys are serialised as missing:X, and we then call
1741
# provider.note_missing(X) for-all X
1742
parents_map = self._unstacked_provider.get_cached_map()
1743
if parents_map is None:
1744
# Repository is not locked, so there's no cache.
1746
# start_set is all the keys in the cache
1747
start_set = set(parents_map)
1748
# result set is all the references to keys in the cache
1749
result_parents = set()
1750
for parents in parents_map.itervalues():
1751
result_parents.update(parents)
1752
stop_keys = result_parents.difference(start_set)
1753
# We don't need to send ghosts back to the server as a position to
1755
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1756
key_count = len(parents_map)
1757
if (NULL_REVISION in result_parents
1758
and NULL_REVISION in self._unstacked_provider.missing_keys):
1759
# If we pruned NULL_REVISION from the stop_keys because it's also
1760
# in our cache of "missing" keys we need to increment our key count
1761
# by 1, because the reconsitituted SearchResult on the server will
1762
# still consider NULL_REVISION to be an included key.
1764
included_keys = start_set.intersection(result_parents)
1765
start_set.difference_update(included_keys)
1766
recipe = ('manual', start_set, stop_keys, key_count)
1767
body = self._serialise_search_recipe(recipe)
1768
path = self.bzrdir._path_for_remote_call(self._client)
1770
if type(key) is not str:
1772
"key %r not a plain string" % (key,))
1773
verb = 'Repository.get_parent_map'
1774
args = (path, 'include-missing:') + tuple(keys)
1776
response = self._call_with_body_bytes_expecting_body(
1778
except errors.UnknownSmartMethod:
1779
# Server does not support this method, so get the whole graph.
1780
# Worse, we have to force a disconnection, because the server now
1781
# doesn't realise it has a body on the wire to consume, so the
1782
# only way to recover is to abandon the connection.
1784
'Server is too old for fast get_parent_map, reconnecting. '
1785
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1787
# To avoid having to disconnect repeatedly, we keep track of the
1788
# fact the server doesn't understand remote methods added in 1.2.
1789
medium._remember_remote_is_before((1, 2))
1790
# Recurse just once and we should use the fallback code.
1791
return self._get_parent_map_rpc(keys)
1792
response_tuple, response_handler = response
1793
if response_tuple[0] not in ['ok']:
1794
response_handler.cancel_read_body()
1795
raise errors.UnexpectedSmartServerResponse(response_tuple)
1796
if response_tuple[0] == 'ok':
1797
coded = bz2.decompress(response_handler.read_body_bytes())
1799
# no revisions found
1801
lines = coded.split('\n')
1804
d = tuple(line.split())
1806
revision_graph[d[0]] = d[1:]
1809
if d[0].startswith('missing:'):
1811
self._unstacked_provider.note_missing_key(revid)
1813
# no parents - so give the Graph result
1815
revision_graph[d[0]] = (NULL_REVISION,)
1816
return revision_graph
1819
def get_signature_text(self, revision_id):
1821
return self._real_repository.get_signature_text(revision_id)
1824
def _get_inventory_xml(self, revision_id):
1826
return self._real_repository._get_inventory_xml(revision_id)
1828
def reconcile(self, other=None, thorough=False):
1830
return self._real_repository.reconcile(other=other, thorough=thorough)
1832
def all_revision_ids(self):
1834
return self._real_repository.all_revision_ids()
1837
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1839
return self._real_repository.get_deltas_for_revisions(revisions,
1840
specific_fileids=specific_fileids)
1843
def get_revision_delta(self, revision_id, specific_fileids=None):
1845
return self._real_repository.get_revision_delta(revision_id,
1846
specific_fileids=specific_fileids)
1849
def revision_trees(self, revision_ids):
1851
return self._real_repository.revision_trees(revision_ids)
1854
def get_revision_reconcile(self, revision_id):
1856
return self._real_repository.get_revision_reconcile(revision_id)
1859
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1861
return self._real_repository.check(revision_ids=revision_ids,
1862
callback_refs=callback_refs, check_repo=check_repo)
1864
def copy_content_into(self, destination, revision_id=None):
1866
return self._real_repository.copy_content_into(
1867
destination, revision_id=revision_id)
1869
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1870
# get a tarball of the remote repository, and copy from that into the
1872
from bzrlib import osutils
1874
# TODO: Maybe a progress bar while streaming the tarball?
1875
note("Copying repository content as tarball...")
1876
tar_file = self._get_tarball('bz2')
1877
if tar_file is None:
1879
destination = to_bzrdir.create_repository()
1881
tar = tarfile.open('repository', fileobj=tar_file,
1883
tmpdir = osutils.mkdtemp()
1885
_extract_tar(tar, tmpdir)
1886
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1887
tmp_repo = tmp_bzrdir.open_repository()
1888
tmp_repo.copy_content_into(destination, revision_id)
1890
osutils.rmtree(tmpdir)
1894
# TODO: Suggestion from john: using external tar is much faster than
1895
# python's tarfile library, but it may not work on windows.
1898
def inventories(self):
1899
"""Decorate the real repository for now.
1901
In the long term a full blown network facility is needed to
1902
avoid creating a real repository object locally.
1905
return self._real_repository.inventories
1908
def pack(self, hint=None, clean_obsolete_packs=False):
1909
"""Compress the data within the repository.
1911
This is not currently implemented within the smart server.
1914
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1917
def revisions(self):
1918
"""Decorate the real repository for now.
1920
In the short term this should become a real object to intercept graph
1923
In the long term a full blown network facility is needed.
1926
return self._real_repository.revisions
1928
def set_make_working_trees(self, new_value):
1930
new_value_str = "True"
1932
new_value_str = "False"
1933
path = self.bzrdir._path_for_remote_call(self._client)
1935
response = self._call(
1936
'Repository.set_make_working_trees', path, new_value_str)
1937
except errors.UnknownSmartMethod:
1939
self._real_repository.set_make_working_trees(new_value)
1941
if response[0] != 'ok':
1942
raise errors.UnexpectedSmartServerResponse(response)
1945
def signatures(self):
1946
"""Decorate the real repository for now.
1948
In the long term a full blown network facility is needed to avoid
1949
creating a real repository object locally.
1952
return self._real_repository.signatures
1955
def sign_revision(self, revision_id, gpg_strategy):
1957
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1961
"""Decorate the real repository for now.
1963
In the long term a full blown network facility is needed to avoid
1964
creating a real repository object locally.
1967
return self._real_repository.texts
1970
def get_revisions(self, revision_ids):
1972
return self._real_repository.get_revisions(revision_ids)
1974
def supports_rich_root(self):
1975
return self._format.rich_root_data
1977
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1978
def iter_reverse_revision_history(self, revision_id):
1980
return self._real_repository.iter_reverse_revision_history(revision_id)
1983
def _serializer(self):
1984
return self._format._serializer
1986
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1988
return self._real_repository.store_revision_signature(
1989
gpg_strategy, plaintext, revision_id)
1991
def add_signature_text(self, revision_id, signature):
1993
return self._real_repository.add_signature_text(revision_id, signature)
1995
def has_signature_for_revision_id(self, revision_id):
1997
return self._real_repository.has_signature_for_revision_id(revision_id)
1999
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2001
return self._real_repository.item_keys_introduced_by(revision_ids,
2002
_files_pb=_files_pb)
2004
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2006
return self._real_repository._find_inconsistent_revision_parents(
2009
def _check_for_inconsistent_revision_parents(self):
2011
return self._real_repository._check_for_inconsistent_revision_parents()
2013
def _make_parents_provider(self, other=None):
2014
providers = [self._unstacked_provider]
2015
if other is not None:
2016
providers.insert(0, other)
2017
return graph.StackedParentsProvider(_LazyListJoin(
2018
providers, self._fallback_repositories))
2020
def _serialise_search_recipe(self, recipe):
2021
"""Serialise a graph search recipe.
2023
:param recipe: A search recipe (start, stop, count).
2024
:return: Serialised bytes.
2026
start_keys = ' '.join(recipe[1])
2027
stop_keys = ' '.join(recipe[2])
2028
count = str(recipe[3])
2029
return '\n'.join((start_keys, stop_keys, count))
2031
def _serialise_search_result(self, search_result):
2032
parts = search_result.get_network_struct()
2033
return '\n'.join(parts)
2036
path = self.bzrdir._path_for_remote_call(self._client)
2038
response = self._call('PackRepository.autopack', path)
2039
except errors.UnknownSmartMethod:
2041
self._real_repository._pack_collection.autopack()
2044
if response[0] != 'ok':
2045
raise errors.UnexpectedSmartServerResponse(response)
2048
class RemoteStreamSink(vf_repository.StreamSink):
2050
def _insert_real(self, stream, src_format, resume_tokens):
2051
self.target_repo._ensure_real()
2052
sink = self.target_repo._real_repository._get_sink()
2053
result = sink.insert_stream(stream, src_format, resume_tokens)
2055
self.target_repo.autopack()
2058
def insert_stream(self, stream, src_format, resume_tokens):
2059
target = self.target_repo
2060
target._unstacked_provider.missing_keys.clear()
2061
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2062
if target._lock_token:
2063
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2064
lock_args = (target._lock_token or '',)
2066
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2068
client = target._client
2069
medium = client._medium
2070
path = target.bzrdir._path_for_remote_call(client)
2071
# Probe for the verb to use with an empty stream before sending the
2072
# real stream to it. We do this both to avoid the risk of sending a
2073
# large request that is then rejected, and because we don't want to
2074
# implement a way to buffer, rewind, or restart the stream.
2076
for verb, required_version in candidate_calls:
2077
if medium._is_remote_before(required_version):
2080
# We've already done the probing (and set _is_remote_before) on
2081
# a previous insert.
2084
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2086
response = client.call_with_body_stream(
2087
(verb, path, '') + lock_args, byte_stream)
2088
except errors.UnknownSmartMethod:
2089
medium._remember_remote_is_before(required_version)
2095
return self._insert_real(stream, src_format, resume_tokens)
2096
self._last_inv_record = None
2097
self._last_substream = None
2098
if required_version < (1, 19):
2099
# Remote side doesn't support inventory deltas. Wrap the stream to
2100
# make sure we don't send any. If the stream contains inventory
2101
# deltas we'll interrupt the smart insert_stream request and
2103
stream = self._stop_stream_if_inventory_delta(stream)
2104
byte_stream = smart_repo._stream_to_byte_stream(
2106
resume_tokens = ' '.join(resume_tokens)
2107
response = client.call_with_body_stream(
2108
(verb, path, resume_tokens) + lock_args, byte_stream)
2109
if response[0][0] not in ('ok', 'missing-basis'):
2110
raise errors.UnexpectedSmartServerResponse(response)
2111
if self._last_substream is not None:
2112
# The stream included an inventory-delta record, but the remote
2113
# side isn't new enough to support them. So we need to send the
2114
# rest of the stream via VFS.
2115
self.target_repo.refresh_data()
2116
return self._resume_stream_with_vfs(response, src_format)
2117
if response[0][0] == 'missing-basis':
2118
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2119
resume_tokens = tokens
2120
return resume_tokens, set(missing_keys)
2122
self.target_repo.refresh_data()
2125
def _resume_stream_with_vfs(self, response, src_format):
2126
"""Resume sending a stream via VFS, first resending the record and
2127
substream that couldn't be sent via an insert_stream verb.
2129
if response[0][0] == 'missing-basis':
2130
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2131
# Ignore missing_keys, we haven't finished inserting yet
2134
def resume_substream():
2135
# Yield the substream that was interrupted.
2136
for record in self._last_substream:
2138
self._last_substream = None
2139
def resume_stream():
2140
# Finish sending the interrupted substream
2141
yield ('inventory-deltas', resume_substream())
2142
# Then simply continue sending the rest of the stream.
2143
for substream_kind, substream in self._last_stream:
2144
yield substream_kind, substream
2145
return self._insert_real(resume_stream(), src_format, tokens)
2147
def _stop_stream_if_inventory_delta(self, stream):
2148
"""Normally this just lets the original stream pass-through unchanged.
2150
However if any 'inventory-deltas' substream occurs it will stop
2151
streaming, and store the interrupted substream and stream in
2152
self._last_substream and self._last_stream so that the stream can be
2153
resumed by _resume_stream_with_vfs.
2156
stream_iter = iter(stream)
2157
for substream_kind, substream in stream_iter:
2158
if substream_kind == 'inventory-deltas':
2159
self._last_substream = substream
2160
self._last_stream = stream_iter
2163
yield substream_kind, substream
2166
class RemoteStreamSource(vf_repository.StreamSource):
2167
"""Stream data from a remote server."""
2169
def get_stream(self, search):
2170
if (self.from_repository._fallback_repositories and
2171
self.to_format._fetch_order == 'topological'):
2172
return self._real_stream(self.from_repository, search)
2175
repos = [self.from_repository]
2181
repos.extend(repo._fallback_repositories)
2182
sources.append(repo)
2183
return self.missing_parents_chain(search, sources)
2185
def get_stream_for_missing_keys(self, missing_keys):
2186
self.from_repository._ensure_real()
2187
real_repo = self.from_repository._real_repository
2188
real_source = real_repo._get_source(self.to_format)
2189
return real_source.get_stream_for_missing_keys(missing_keys)
2191
def _real_stream(self, repo, search):
2192
"""Get a stream for search from repo.
2194
This never called RemoteStreamSource.get_stream, and is a heler
2195
for RemoteStreamSource._get_stream to allow getting a stream
2196
reliably whether fallback back because of old servers or trying
2197
to stream from a non-RemoteRepository (which the stacked support
2200
source = repo._get_source(self.to_format)
2201
if isinstance(source, RemoteStreamSource):
2203
source = repo._real_repository._get_source(self.to_format)
2204
return source.get_stream(search)
2206
def _get_stream(self, repo, search):
2207
"""Core worker to get a stream from repo for search.
2209
This is used by both get_stream and the stacking support logic. It
2210
deliberately gets a stream for repo which does not need to be
2211
self.from_repository. In the event that repo is not Remote, or
2212
cannot do a smart stream, a fallback is made to the generic
2213
repository._get_stream() interface, via self._real_stream.
2215
In the event of stacking, streams from _get_stream will not
2216
contain all the data for search - this is normal (see get_stream).
2218
:param repo: A repository.
2219
:param search: A search.
2221
# Fallbacks may be non-smart
2222
if not isinstance(repo, RemoteRepository):
2223
return self._real_stream(repo, search)
2224
client = repo._client
2225
medium = client._medium
2226
path = repo.bzrdir._path_for_remote_call(client)
2227
search_bytes = repo._serialise_search_result(search)
2228
args = (path, self.to_format.network_name())
2230
('Repository.get_stream_1.19', (1, 19)),
2231
('Repository.get_stream', (1, 13))]
2234
for verb, version in candidate_verbs:
2235
if medium._is_remote_before(version):
2238
response = repo._call_with_body_bytes_expecting_body(
2239
verb, args, search_bytes)
2240
except errors.UnknownSmartMethod:
2241
medium._remember_remote_is_before(version)
2242
except errors.UnknownErrorFromSmartServer, e:
2243
if isinstance(search, graph.EverythingResult):
2244
error_verb = e.error_from_smart_server.error_verb
2245
if error_verb == 'BadSearch':
2246
# Pre-2.4 servers don't support this sort of search.
2247
# XXX: perhaps falling back to VFS on BadSearch is a
2248
# good idea in general? It might provide a little bit
2249
# of protection against client-side bugs.
2250
medium._remember_remote_is_before((2, 4))
2254
response_tuple, response_handler = response
2258
return self._real_stream(repo, search)
2259
if response_tuple[0] != 'ok':
2260
raise errors.UnexpectedSmartServerResponse(response_tuple)
2261
byte_stream = response_handler.read_streamed_body()
2262
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2263
self._record_counter)
2264
if src_format.network_name() != repo._format.network_name():
2265
raise AssertionError(
2266
"Mismatched RemoteRepository and stream src %r, %r" % (
2267
src_format.network_name(), repo._format.network_name()))
2270
def missing_parents_chain(self, search, sources):
2271
"""Chain multiple streams together to handle stacking.
2273
:param search: The overall search to satisfy with streams.
2274
:param sources: A list of Repository objects to query.
2276
self.from_serialiser = self.from_repository._format._serializer
2277
self.seen_revs = set()
2278
self.referenced_revs = set()
2279
# If there are heads in the search, or the key count is > 0, we are not
2281
while not search.is_empty() and len(sources) > 1:
2282
source = sources.pop(0)
2283
stream = self._get_stream(source, search)
2284
for kind, substream in stream:
2285
if kind != 'revisions':
2286
yield kind, substream
2288
yield kind, self.missing_parents_rev_handler(substream)
2289
search = search.refine(self.seen_revs, self.referenced_revs)
2290
self.seen_revs = set()
2291
self.referenced_revs = set()
2292
if not search.is_empty():
2293
for kind, stream in self._get_stream(sources[0], search):
2296
def missing_parents_rev_handler(self, substream):
2297
for content in substream:
2298
revision_bytes = content.get_bytes_as('fulltext')
2299
revision = self.from_serialiser.read_revision_from_string(
2301
self.seen_revs.add(content.key[-1])
2302
self.referenced_revs.update(revision.parent_ids)
2306
class RemoteBranchLockableFiles(LockableFiles):
2307
"""A 'LockableFiles' implementation that talks to a smart server.
2309
This is not a public interface class.
2312
def __init__(self, bzrdir, _client):
2313
self.bzrdir = bzrdir
2314
self._client = _client
2315
self._need_find_modes = True
2316
LockableFiles.__init__(
2317
self, bzrdir.get_branch_transport(None),
2318
'lock', lockdir.LockDir)
2320
def _find_modes(self):
2321
# RemoteBranches don't let the client set the mode of control files.
2322
self._dir_mode = None
2323
self._file_mode = None
2326
class RemoteBranchFormat(branch.BranchFormat):
2328
def __init__(self, network_name=None):
2329
super(RemoteBranchFormat, self).__init__()
2330
self._matchingbzrdir = RemoteBzrDirFormat()
2331
self._matchingbzrdir.set_branch_format(self)
2332
self._custom_format = None
2333
self._network_name = network_name
2335
def __eq__(self, other):
2336
return (isinstance(other, RemoteBranchFormat) and
2337
self.__dict__ == other.__dict__)
2339
def _ensure_real(self):
2340
if self._custom_format is None:
2341
self._custom_format = branch.network_format_registry.get(
2344
def get_format_description(self):
2346
return 'Remote: ' + self._custom_format.get_format_description()
2348
def network_name(self):
2349
return self._network_name
2351
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2352
return a_bzrdir.open_branch(name=name,
2353
ignore_fallbacks=ignore_fallbacks)
2355
def _vfs_initialize(self, a_bzrdir, name):
2356
# Initialisation when using a local bzrdir object, or a non-vfs init
2357
# method is not available on the server.
2358
# self._custom_format is always set - the start of initialize ensures
2360
if isinstance(a_bzrdir, RemoteBzrDir):
2361
a_bzrdir._ensure_real()
2362
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2365
# We assume the bzrdir is parameterised; it may not be.
2366
result = self._custom_format.initialize(a_bzrdir, name)
2367
if (isinstance(a_bzrdir, RemoteBzrDir) and
2368
not isinstance(result, RemoteBranch)):
2369
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2373
def initialize(self, a_bzrdir, name=None, repository=None):
2374
# 1) get the network name to use.
2375
if self._custom_format:
2376
network_name = self._custom_format.network_name()
2378
# Select the current bzrlib default and ask for that.
2379
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2380
reference_format = reference_bzrdir_format.get_branch_format()
2381
self._custom_format = reference_format
2382
network_name = reference_format.network_name()
2383
# Being asked to create on a non RemoteBzrDir:
2384
if not isinstance(a_bzrdir, RemoteBzrDir):
2385
return self._vfs_initialize(a_bzrdir, name=name)
2386
medium = a_bzrdir._client._medium
2387
if medium._is_remote_before((1, 13)):
2388
return self._vfs_initialize(a_bzrdir, name=name)
2389
# Creating on a remote bzr dir.
2390
# 2) try direct creation via RPC
2391
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2392
if name is not None:
2393
# XXX JRV20100304: Support creating colocated branches
2394
raise errors.NoColocatedBranchSupport(self)
2395
verb = 'BzrDir.create_branch'
2397
response = a_bzrdir._call(verb, path, network_name)
2398
except errors.UnknownSmartMethod:
2399
# Fallback - use vfs methods
2400
medium._remember_remote_is_before((1, 13))
2401
return self._vfs_initialize(a_bzrdir, name=name)
2402
if response[0] != 'ok':
2403
raise errors.UnexpectedSmartServerResponse(response)
2404
# Turn the response into a RemoteRepository object.
2405
format = RemoteBranchFormat(network_name=response[1])
2406
repo_format = response_tuple_to_repo_format(response[3:])
2407
repo_path = response[2]
2408
if repository is not None:
2409
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2410
url_diff = urlutils.relative_url(repository.user_url,
2413
raise AssertionError(
2414
'repository.user_url %r does not match URL from server '
2415
'response (%r + %r)'
2416
% (repository.user_url, a_bzrdir.user_url, repo_path))
2417
remote_repo = repository
2420
repo_bzrdir = a_bzrdir
2422
repo_bzrdir = RemoteBzrDir(
2423
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2425
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2426
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2427
format=format, setup_stacking=False, name=name)
2428
# XXX: We know this is a new branch, so it must have revno 0, revid
2429
# NULL_REVISION. Creating the branch locked would make this be unable
2430
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2431
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2432
return remote_branch
2434
def make_tags(self, branch):
2436
return self._custom_format.make_tags(branch)
2438
def supports_tags(self):
2439
# Remote branches might support tags, but we won't know until we
2440
# access the real remote branch.
2442
return self._custom_format.supports_tags()
2444
def supports_stacking(self):
2446
return self._custom_format.supports_stacking()
2448
def supports_set_append_revisions_only(self):
2450
return self._custom_format.supports_set_append_revisions_only()
2452
def _use_default_local_heads_to_fetch(self):
2453
# If the branch format is a metadir format *and* its heads_to_fetch
2454
# implementation is not overridden vs the base class, we can use the
2455
# base class logic rather than use the heads_to_fetch RPC. This is
2456
# usually cheaper in terms of net round trips, as the last-revision and
2457
# tags info fetched is cached and would be fetched anyway.
2459
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2460
branch_class = self._custom_format._branch_class()
2461
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2462
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2466
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2467
"""Branch stored on a server accessed by HPSS RPC.
2469
At the moment most operations are mapped down to simple file operations.
2472
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2473
_client=None, format=None, setup_stacking=True, name=None):
2474
"""Create a RemoteBranch instance.
2476
:param real_branch: An optional local implementation of the branch
2477
format, usually accessing the data via the VFS.
2478
:param _client: Private parameter for testing.
2479
:param format: A RemoteBranchFormat object, None to create one
2480
automatically. If supplied it should have a network_name already
2482
:param setup_stacking: If True make an RPC call to determine the
2483
stacked (or not) status of the branch. If False assume the branch
2485
:param name: Colocated branch name
2487
# We intentionally don't call the parent class's __init__, because it
2488
# will try to assign to self.tags, which is a property in this subclass.
2489
# And the parent's __init__ doesn't do much anyway.
2490
self.bzrdir = remote_bzrdir
2491
if _client is not None:
2492
self._client = _client
2494
self._client = remote_bzrdir._client
2495
self.repository = remote_repository
2496
if real_branch is not None:
2497
self._real_branch = real_branch
2498
# Give the remote repository the matching real repo.
2499
real_repo = self._real_branch.repository
2500
if isinstance(real_repo, RemoteRepository):
2501
real_repo._ensure_real()
2502
real_repo = real_repo._real_repository
2503
self.repository._set_real_repository(real_repo)
2504
# Give the branch the remote repository to let fast-pathing happen.
2505
self._real_branch.repository = self.repository
2507
self._real_branch = None
2508
# Fill out expected attributes of branch for bzrlib API users.
2509
self._clear_cached_state()
2510
# TODO: deprecate self.base in favor of user_url
2511
self.base = self.bzrdir.user_url
2513
self._control_files = None
2514
self._lock_mode = None
2515
self._lock_token = None
2516
self._repo_lock_token = None
2517
self._lock_count = 0
2518
self._leave_lock = False
2519
# Setup a format: note that we cannot call _ensure_real until all the
2520
# attributes above are set: This code cannot be moved higher up in this
2523
self._format = RemoteBranchFormat()
2524
if real_branch is not None:
2525
self._format._network_name = \
2526
self._real_branch._format.network_name()
2528
self._format = format
2529
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2530
# branch.open_branch method.
2531
self._real_ignore_fallbacks = not setup_stacking
2532
if not self._format._network_name:
2533
# Did not get from open_branchV2 - old server.
2535
self._format._network_name = \
2536
self._real_branch._format.network_name()
2537
self.tags = self._format.make_tags(self)
2538
# The base class init is not called, so we duplicate this:
2539
hooks = branch.Branch.hooks['open']
2542
self._is_stacked = False
2544
self._setup_stacking()
2546
def _setup_stacking(self):
2547
# configure stacking into the remote repository, by reading it from
2550
fallback_url = self.get_stacked_on_url()
2551
except (errors.NotStacked, errors.UnstackableBranchFormat,
2552
errors.UnstackableRepositoryFormat), e:
2554
self._is_stacked = True
2555
self._activate_fallback_location(fallback_url)
2557
def _get_config(self):
2558
return RemoteBranchConfig(self)
2560
def _get_real_transport(self):
2561
# if we try vfs access, return the real branch's vfs transport
2563
return self._real_branch._transport
2565
_transport = property(_get_real_transport)
2568
return "%s(%s)" % (self.__class__.__name__, self.base)
2572
def _ensure_real(self):
2573
"""Ensure that there is a _real_branch set.
2575
Used before calls to self._real_branch.
2577
if self._real_branch is None:
2578
if not vfs.vfs_enabled():
2579
raise AssertionError('smart server vfs must be enabled '
2580
'to use vfs implementation')
2581
self.bzrdir._ensure_real()
2582
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2583
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2584
if self.repository._real_repository is None:
2585
# Give the remote repository the matching real repo.
2586
real_repo = self._real_branch.repository
2587
if isinstance(real_repo, RemoteRepository):
2588
real_repo._ensure_real()
2589
real_repo = real_repo._real_repository
2590
self.repository._set_real_repository(real_repo)
2591
# Give the real branch the remote repository to let fast-pathing
2593
self._real_branch.repository = self.repository
2594
if self._lock_mode == 'r':
2595
self._real_branch.lock_read()
2596
elif self._lock_mode == 'w':
2597
self._real_branch.lock_write(token=self._lock_token)
2599
def _translate_error(self, err, **context):
2600
self.repository._translate_error(err, branch=self, **context)
2602
def _clear_cached_state(self):
2603
super(RemoteBranch, self)._clear_cached_state()
2604
if self._real_branch is not None:
2605
self._real_branch._clear_cached_state()
2607
def _clear_cached_state_of_remote_branch_only(self):
2608
"""Like _clear_cached_state, but doesn't clear the cache of
2611
This is useful when falling back to calling a method of
2612
self._real_branch that changes state. In that case the underlying
2613
branch changes, so we need to invalidate this RemoteBranch's cache of
2614
it. However, there's no need to invalidate the _real_branch's cache
2615
too, in fact doing so might harm performance.
2617
super(RemoteBranch, self)._clear_cached_state()
2620
def control_files(self):
2621
# Defer actually creating RemoteBranchLockableFiles until its needed,
2622
# because it triggers an _ensure_real that we otherwise might not need.
2623
if self._control_files is None:
2624
self._control_files = RemoteBranchLockableFiles(
2625
self.bzrdir, self._client)
2626
return self._control_files
2628
def _get_checkout_format(self):
2630
return self._real_branch._get_checkout_format()
2632
def get_physical_lock_status(self):
2633
"""See Branch.get_physical_lock_status()."""
2634
# should be an API call to the server, as branches must be lockable.
2636
return self._real_branch.get_physical_lock_status()
2638
def get_stacked_on_url(self):
2639
"""Get the URL this branch is stacked against.
2641
:raises NotStacked: If the branch is not stacked.
2642
:raises UnstackableBranchFormat: If the branch does not support
2644
:raises UnstackableRepositoryFormat: If the repository does not support
2648
# there may not be a repository yet, so we can't use
2649
# self._translate_error, so we can't use self._call either.
2650
response = self._client.call('Branch.get_stacked_on_url',
2651
self._remote_path())
2652
except errors.ErrorFromSmartServer, err:
2653
# there may not be a repository yet, so we can't call through
2654
# its _translate_error
2655
_translate_error(err, branch=self)
2656
except errors.UnknownSmartMethod, err:
2658
return self._real_branch.get_stacked_on_url()
2659
if response[0] != 'ok':
2660
raise errors.UnexpectedSmartServerResponse(response)
2663
def set_stacked_on_url(self, url):
2664
branch.Branch.set_stacked_on_url(self, url)
2666
self._is_stacked = False
2668
self._is_stacked = True
2670
def _vfs_get_tags_bytes(self):
2672
return self._real_branch._get_tags_bytes()
2675
def _get_tags_bytes(self):
2676
if self._tags_bytes is None:
2677
self._tags_bytes = self._get_tags_bytes_via_hpss()
2678
return self._tags_bytes
2680
def _get_tags_bytes_via_hpss(self):
2681
medium = self._client._medium
2682
if medium._is_remote_before((1, 13)):
2683
return self._vfs_get_tags_bytes()
2685
response = self._call('Branch.get_tags_bytes', self._remote_path())
2686
except errors.UnknownSmartMethod:
2687
medium._remember_remote_is_before((1, 13))
2688
return self._vfs_get_tags_bytes()
2691
def _vfs_set_tags_bytes(self, bytes):
2693
return self._real_branch._set_tags_bytes(bytes)
2695
def _set_tags_bytes(self, bytes):
2696
if self.is_locked():
2697
self._tags_bytes = bytes
2698
medium = self._client._medium
2699
if medium._is_remote_before((1, 18)):
2700
self._vfs_set_tags_bytes(bytes)
2704
self._remote_path(), self._lock_token, self._repo_lock_token)
2705
response = self._call_with_body_bytes(
2706
'Branch.set_tags_bytes', args, bytes)
2707
except errors.UnknownSmartMethod:
2708
medium._remember_remote_is_before((1, 18))
2709
self._vfs_set_tags_bytes(bytes)
2711
def lock_read(self):
2712
"""Lock the branch for read operations.
2714
:return: A bzrlib.lock.LogicalLockResult.
2716
self.repository.lock_read()
2717
if not self._lock_mode:
2718
self._note_lock('r')
2719
self._lock_mode = 'r'
2720
self._lock_count = 1
2721
if self._real_branch is not None:
2722
self._real_branch.lock_read()
2724
self._lock_count += 1
2725
return lock.LogicalLockResult(self.unlock)
2727
def _remote_lock_write(self, token):
2729
branch_token = repo_token = ''
2731
branch_token = token
2732
repo_token = self.repository.lock_write().repository_token
2733
self.repository.unlock()
2734
err_context = {'token': token}
2736
response = self._call(
2737
'Branch.lock_write', self._remote_path(), branch_token,
2738
repo_token or '', **err_context)
2739
except errors.LockContention, e:
2740
# The LockContention from the server doesn't have any
2741
# information about the lock_url. We re-raise LockContention
2742
# with valid lock_url.
2743
raise errors.LockContention('(remote lock)',
2744
self.repository.base.split('.bzr/')[0])
2745
if response[0] != 'ok':
2746
raise errors.UnexpectedSmartServerResponse(response)
2747
ok, branch_token, repo_token = response
2748
return branch_token, repo_token
2750
def lock_write(self, token=None):
2751
if not self._lock_mode:
2752
self._note_lock('w')
2753
# Lock the branch and repo in one remote call.
2754
remote_tokens = self._remote_lock_write(token)
2755
self._lock_token, self._repo_lock_token = remote_tokens
2756
if not self._lock_token:
2757
raise SmartProtocolError('Remote server did not return a token!')
2758
# Tell the self.repository object that it is locked.
2759
self.repository.lock_write(
2760
self._repo_lock_token, _skip_rpc=True)
2762
if self._real_branch is not None:
2763
self._real_branch.lock_write(token=self._lock_token)
2764
if token is not None:
2765
self._leave_lock = True
2767
self._leave_lock = False
2768
self._lock_mode = 'w'
2769
self._lock_count = 1
2770
elif self._lock_mode == 'r':
2771
raise errors.ReadOnlyError(self)
2773
if token is not None:
2774
# A token was given to lock_write, and we're relocking, so
2775
# check that the given token actually matches the one we
2777
if token != self._lock_token:
2778
raise errors.TokenMismatch(token, self._lock_token)
2779
self._lock_count += 1
2780
# Re-lock the repository too.
2781
self.repository.lock_write(self._repo_lock_token)
2782
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2784
def _unlock(self, branch_token, repo_token):
2785
err_context = {'token': str((branch_token, repo_token))}
2786
response = self._call(
2787
'Branch.unlock', self._remote_path(), branch_token,
2788
repo_token or '', **err_context)
2789
if response == ('ok',):
2791
raise errors.UnexpectedSmartServerResponse(response)
2793
@only_raises(errors.LockNotHeld, errors.LockBroken)
2796
self._lock_count -= 1
2797
if not self._lock_count:
2798
self._clear_cached_state()
2799
mode = self._lock_mode
2800
self._lock_mode = None
2801
if self._real_branch is not None:
2802
if (not self._leave_lock and mode == 'w' and
2803
self._repo_lock_token):
2804
# If this RemoteBranch will remove the physical lock
2805
# for the repository, make sure the _real_branch
2806
# doesn't do it first. (Because the _real_branch's
2807
# repository is set to be the RemoteRepository.)
2808
self._real_branch.repository.leave_lock_in_place()
2809
self._real_branch.unlock()
2811
# Only write-locked branched need to make a remote method
2812
# call to perform the unlock.
2814
if not self._lock_token:
2815
raise AssertionError('Locked, but no token!')
2816
branch_token = self._lock_token
2817
repo_token = self._repo_lock_token
2818
self._lock_token = None
2819
self._repo_lock_token = None
2820
if not self._leave_lock:
2821
self._unlock(branch_token, repo_token)
2823
self.repository.unlock()
2825
def break_lock(self):
2827
return self._real_branch.break_lock()
2829
def leave_lock_in_place(self):
2830
if not self._lock_token:
2831
raise NotImplementedError(self.leave_lock_in_place)
2832
self._leave_lock = True
2834
def dont_leave_lock_in_place(self):
2835
if not self._lock_token:
2836
raise NotImplementedError(self.dont_leave_lock_in_place)
2837
self._leave_lock = False
2840
def get_rev_id(self, revno, history=None):
2842
return _mod_revision.NULL_REVISION
2843
last_revision_info = self.last_revision_info()
2844
ok, result = self.repository.get_rev_id_for_revno(
2845
revno, last_revision_info)
2848
missing_parent = result[1]
2849
# Either the revision named by the server is missing, or its parent
2850
# is. Call get_parent_map to determine which, so that we report a
2852
parent_map = self.repository.get_parent_map([missing_parent])
2853
if missing_parent in parent_map:
2854
missing_parent = parent_map[missing_parent]
2855
raise errors.RevisionNotPresent(missing_parent, self.repository)
2857
def _read_last_revision_info(self):
2858
response = self._call('Branch.last_revision_info', self._remote_path())
2859
if response[0] != 'ok':
2860
raise SmartProtocolError('unexpected response code %s' % (response,))
2861
revno = int(response[1])
2862
last_revision = response[2]
2863
return (revno, last_revision)
2865
def _gen_revision_history(self):
2866
"""See Branch._gen_revision_history()."""
2867
if self._is_stacked:
2869
return self._real_branch._gen_revision_history()
2870
response_tuple, response_handler = self._call_expecting_body(
2871
'Branch.revision_history', self._remote_path())
2872
if response_tuple[0] != 'ok':
2873
raise errors.UnexpectedSmartServerResponse(response_tuple)
2874
result = response_handler.read_body_bytes().split('\x00')
2879
def _remote_path(self):
2880
return self.bzrdir._path_for_remote_call(self._client)
2882
def _set_last_revision_descendant(self, revision_id, other_branch,
2883
allow_diverged=False, allow_overwrite_descendant=False):
2884
# This performs additional work to meet the hook contract; while its
2885
# undesirable, we have to synthesise the revno to call the hook, and
2886
# not calling the hook is worse as it means changes can't be prevented.
2887
# Having calculated this though, we can't just call into
2888
# set_last_revision_info as a simple call, because there is a set_rh
2889
# hook that some folk may still be using.
2890
old_revno, old_revid = self.last_revision_info()
2891
history = self._lefthand_history(revision_id)
2892
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2893
err_context = {'other_branch': other_branch}
2894
response = self._call('Branch.set_last_revision_ex',
2895
self._remote_path(), self._lock_token, self._repo_lock_token,
2896
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2898
self._clear_cached_state()
2899
if len(response) != 3 and response[0] != 'ok':
2900
raise errors.UnexpectedSmartServerResponse(response)
2901
new_revno, new_revision_id = response[1:]
2902
self._last_revision_info_cache = new_revno, new_revision_id
2903
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2904
if self._real_branch is not None:
2905
cache = new_revno, new_revision_id
2906
self._real_branch._last_revision_info_cache = cache
2908
def _set_last_revision(self, revision_id):
2909
old_revno, old_revid = self.last_revision_info()
2910
# This performs additional work to meet the hook contract; while its
2911
# undesirable, we have to synthesise the revno to call the hook, and
2912
# not calling the hook is worse as it means changes can't be prevented.
2913
# Having calculated this though, we can't just call into
2914
# set_last_revision_info as a simple call, because there is a set_rh
2915
# hook that some folk may still be using.
2916
history = self._lefthand_history(revision_id)
2917
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2918
self._clear_cached_state()
2919
response = self._call('Branch.set_last_revision',
2920
self._remote_path(), self._lock_token, self._repo_lock_token,
2922
if response != ('ok',):
2923
raise errors.UnexpectedSmartServerResponse(response)
2924
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2926
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2928
def set_revision_history(self, rev_history):
2929
"""See Branch.set_revision_history."""
2930
self._set_revision_history(rev_history)
2933
def _set_revision_history(self, rev_history):
2934
# Send just the tip revision of the history; the server will generate
2935
# the full history from that. If the revision doesn't exist in this
2936
# branch, NoSuchRevision will be raised.
2937
if rev_history == []:
2940
rev_id = rev_history[-1]
2941
self._set_last_revision(rev_id)
2942
for hook in branch.Branch.hooks['set_rh']:
2943
hook(self, rev_history)
2944
self._cache_revision_history(rev_history)
2946
def _get_parent_location(self):
2947
medium = self._client._medium
2948
if medium._is_remote_before((1, 13)):
2949
return self._vfs_get_parent_location()
2951
response = self._call('Branch.get_parent', self._remote_path())
2952
except errors.UnknownSmartMethod:
2953
medium._remember_remote_is_before((1, 13))
2954
return self._vfs_get_parent_location()
2955
if len(response) != 1:
2956
raise errors.UnexpectedSmartServerResponse(response)
2957
parent_location = response[0]
2958
if parent_location == '':
2960
return parent_location
2962
def _vfs_get_parent_location(self):
2964
return self._real_branch._get_parent_location()
2966
def _set_parent_location(self, url):
2967
medium = self._client._medium
2968
if medium._is_remote_before((1, 15)):
2969
return self._vfs_set_parent_location(url)
2971
call_url = url or ''
2972
if type(call_url) is not str:
2973
raise AssertionError('url must be a str or None (%s)' % url)
2974
response = self._call('Branch.set_parent_location',
2975
self._remote_path(), self._lock_token, self._repo_lock_token,
2977
except errors.UnknownSmartMethod:
2978
medium._remember_remote_is_before((1, 15))
2979
return self._vfs_set_parent_location(url)
2981
raise errors.UnexpectedSmartServerResponse(response)
2983
def _vfs_set_parent_location(self, url):
2985
return self._real_branch._set_parent_location(url)
2988
def pull(self, source, overwrite=False, stop_revision=None,
2990
self._clear_cached_state_of_remote_branch_only()
2992
return self._real_branch.pull(
2993
source, overwrite=overwrite, stop_revision=stop_revision,
2994
_override_hook_target=self, **kwargs)
2997
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
2999
return self._real_branch.push(
3000
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3001
_override_hook_source_branch=self)
3003
def is_locked(self):
3004
return self._lock_count >= 1
3007
def revision_id_to_revno(self, revision_id):
3009
return self._real_branch.revision_id_to_revno(revision_id)
3012
def set_last_revision_info(self, revno, revision_id):
3013
# XXX: These should be returned by the set_last_revision_info verb
3014
old_revno, old_revid = self.last_revision_info()
3015
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3016
if not revision_id or not isinstance(revision_id, basestring):
3017
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3019
response = self._call('Branch.set_last_revision_info',
3020
self._remote_path(), self._lock_token, self._repo_lock_token,
3021
str(revno), revision_id)
3022
except errors.UnknownSmartMethod:
3024
self._clear_cached_state_of_remote_branch_only()
3025
self._real_branch.set_last_revision_info(revno, revision_id)
3026
self._last_revision_info_cache = revno, revision_id
3028
if response == ('ok',):
3029
self._clear_cached_state()
3030
self._last_revision_info_cache = revno, revision_id
3031
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3032
# Update the _real_branch's cache too.
3033
if self._real_branch is not None:
3034
cache = self._last_revision_info_cache
3035
self._real_branch._last_revision_info_cache = cache
3037
raise errors.UnexpectedSmartServerResponse(response)
3040
def generate_revision_history(self, revision_id, last_rev=None,
3042
medium = self._client._medium
3043
if not medium._is_remote_before((1, 6)):
3044
# Use a smart method for 1.6 and above servers
3046
self._set_last_revision_descendant(revision_id, other_branch,
3047
allow_diverged=True, allow_overwrite_descendant=True)
3049
except errors.UnknownSmartMethod:
3050
medium._remember_remote_is_before((1, 6))
3051
self._clear_cached_state_of_remote_branch_only()
3052
self._set_revision_history(self._lefthand_history(revision_id,
3053
last_rev=last_rev,other_branch=other_branch))
3055
def set_push_location(self, location):
3057
return self._real_branch.set_push_location(location)
3059
def heads_to_fetch(self):
3060
if self._format._use_default_local_heads_to_fetch():
3061
# We recognise this format, and its heads-to-fetch implementation
3062
# is the default one (tip + tags). In this case it's cheaper to
3063
# just use the default implementation rather than a special RPC as
3064
# the tip and tags data is cached.
3065
return branch.Branch.heads_to_fetch(self)
3066
medium = self._client._medium
3067
if medium._is_remote_before((2, 4)):
3068
return self._vfs_heads_to_fetch()
3070
return self._rpc_heads_to_fetch()
3071
except errors.UnknownSmartMethod:
3072
medium._remember_remote_is_before((2, 4))
3073
return self._vfs_heads_to_fetch()
3075
def _rpc_heads_to_fetch(self):
3076
response = self._call('Branch.heads_to_fetch', self._remote_path())
3077
if len(response) != 2:
3078
raise errors.UnexpectedSmartServerResponse(response)
3079
must_fetch, if_present_fetch = response
3080
return set(must_fetch), set(if_present_fetch)
3082
def _vfs_heads_to_fetch(self):
3084
return self._real_branch.heads_to_fetch()
3087
class RemoteConfig(object):
3088
"""A Config that reads and writes from smart verbs.
3090
It is a low-level object that considers config data to be name/value pairs
3091
that may be associated with a section. Assigning meaning to the these
3092
values is done at higher levels like bzrlib.config.TreeConfig.
3095
def get_option(self, name, section=None, default=None):
3096
"""Return the value associated with a named option.
3098
:param name: The name of the value
3099
:param section: The section the option is in (if any)
3100
:param default: The value to return if the value is not set
3101
:return: The value or default value
3104
configobj = self._get_configobj()
3107
section_obj = configobj
3110
section_obj = configobj[section]
3113
if section_obj is None:
3116
value = section_obj.get(name, default)
3117
except errors.UnknownSmartMethod:
3118
value = self._vfs_get_option(name, section, default)
3119
for hook in config.OldConfigHooks['get']:
3120
hook(self, name, value)
3123
def _response_to_configobj(self, response):
3124
if len(response[0]) and response[0][0] != 'ok':
3125
raise errors.UnexpectedSmartServerResponse(response)
3126
lines = response[1].read_body_bytes().splitlines()
3127
conf = config.ConfigObj(lines, encoding='utf-8')
3128
for hook in config.OldConfigHooks['load']:
3133
class RemoteBranchConfig(RemoteConfig):
3134
"""A RemoteConfig for Branches."""
3136
def __init__(self, branch):
3137
self._branch = branch
3139
def _get_configobj(self):
3140
path = self._branch._remote_path()
3141
response = self._branch._client.call_expecting_body(
3142
'Branch.get_config_file', path)
3143
return self._response_to_configobj(response)
3145
def set_option(self, value, name, section=None):
3146
"""Set the value associated with a named option.
3148
:param value: The value to set
3149
:param name: The name of the value to set
3150
:param section: The section the option is in (if any)
3152
medium = self._branch._client._medium
3153
if medium._is_remote_before((1, 14)):
3154
return self._vfs_set_option(value, name, section)
3155
if isinstance(value, dict):
3156
if medium._is_remote_before((2, 2)):
3157
return self._vfs_set_option(value, name, section)
3158
return self._set_config_option_dict(value, name, section)
3160
return self._set_config_option(value, name, section)
3162
def _set_config_option(self, value, name, section):
3164
path = self._branch._remote_path()
3165
response = self._branch._client.call('Branch.set_config_option',
3166
path, self._branch._lock_token, self._branch._repo_lock_token,
3167
value.encode('utf8'), name, section or '')
3168
except errors.UnknownSmartMethod:
3169
medium = self._branch._client._medium
3170
medium._remember_remote_is_before((1, 14))
3171
return self._vfs_set_option(value, name, section)
3173
raise errors.UnexpectedSmartServerResponse(response)
3175
def _serialize_option_dict(self, option_dict):
3177
for key, value in option_dict.items():
3178
if isinstance(key, unicode):
3179
key = key.encode('utf8')
3180
if isinstance(value, unicode):
3181
value = value.encode('utf8')
3182
utf8_dict[key] = value
3183
return bencode.bencode(utf8_dict)
3185
def _set_config_option_dict(self, value, name, section):
3187
path = self._branch._remote_path()
3188
serialised_dict = self._serialize_option_dict(value)
3189
response = self._branch._client.call(
3190
'Branch.set_config_option_dict',
3191
path, self._branch._lock_token, self._branch._repo_lock_token,
3192
serialised_dict, name, section or '')
3193
except errors.UnknownSmartMethod:
3194
medium = self._branch._client._medium
3195
medium._remember_remote_is_before((2, 2))
3196
return self._vfs_set_option(value, name, section)
3198
raise errors.UnexpectedSmartServerResponse(response)
3200
def _real_object(self):
3201
self._branch._ensure_real()
3202
return self._branch._real_branch
3204
def _vfs_set_option(self, value, name, section=None):
3205
return self._real_object()._get_config().set_option(
3206
value, name, section)
3209
class RemoteBzrDirConfig(RemoteConfig):
3210
"""A RemoteConfig for BzrDirs."""
3212
def __init__(self, bzrdir):
3213
self._bzrdir = bzrdir
3215
def _get_configobj(self):
3216
medium = self._bzrdir._client._medium
3217
verb = 'BzrDir.get_config_file'
3218
if medium._is_remote_before((1, 15)):
3219
raise errors.UnknownSmartMethod(verb)
3220
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3221
response = self._bzrdir._call_expecting_body(
3223
return self._response_to_configobj(response)
3225
def _vfs_get_option(self, name, section, default):
3226
return self._real_object()._get_config().get_option(
3227
name, section, default)
3229
def set_option(self, value, name, section=None):
3230
"""Set the value associated with a named option.
3232
:param value: The value to set
3233
:param name: The name of the value to set
3234
:param section: The section the option is in (if any)
3236
return self._real_object()._get_config().set_option(
3237
value, name, section)
3239
def _real_object(self):
3240
self._bzrdir._ensure_real()
3241
return self._bzrdir._real_bzrdir
3245
def _extract_tar(tar, to_dir):
3246
"""Extract all the contents of a tarfile object.
3248
A replacement for extractall, which is not present in python2.4
3251
tar.extract(tarinfo, to_dir)
3254
def _translate_error(err, **context):
3255
"""Translate an ErrorFromSmartServer into a more useful error.
3257
Possible context keys:
3265
If the error from the server doesn't match a known pattern, then
3266
UnknownErrorFromSmartServer is raised.
3270
return context[name]
3271
except KeyError, key_err:
3272
mutter('Missing key %r in context %r', key_err.args[0], context)
3275
"""Get the path from the context if present, otherwise use first error
3279
return context['path']
3280
except KeyError, key_err:
3282
return err.error_args[0]
3283
except IndexError, idx_err:
3285
'Missing key %r in context %r', key_err.args[0], context)
3288
if err.error_verb == 'NoSuchRevision':
3289
raise NoSuchRevision(find('branch'), err.error_args[0])
3290
elif err.error_verb == 'nosuchrevision':
3291
raise NoSuchRevision(find('repository'), err.error_args[0])
3292
elif err.error_verb == 'nobranch':
3293
if len(err.error_args) >= 1:
3294
extra = err.error_args[0]
3297
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3299
elif err.error_verb == 'norepository':
3300
raise errors.NoRepositoryPresent(find('bzrdir'))
3301
elif err.error_verb == 'UnlockableTransport':
3302
raise errors.UnlockableTransport(find('bzrdir').root_transport)
3303
elif err.error_verb == 'TokenMismatch':
3304
raise errors.TokenMismatch(find('token'), '(remote token)')
3305
elif err.error_verb == 'Diverged':
3306
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3307
elif err.error_verb == 'NotStacked':
3308
raise errors.NotStacked(branch=find('branch'))
3309
elif err.error_verb == 'PermissionDenied':
3311
if len(err.error_args) >= 2:
3312
extra = err.error_args[1]
3315
raise errors.PermissionDenied(path, extra=extra)
3316
elif err.error_verb == 'ReadError':
3318
raise errors.ReadError(path)
3319
elif err.error_verb == 'NoSuchFile':
3321
raise errors.NoSuchFile(path)
3322
_translate_error_without_context(err)
3325
def _translate_error_without_context(err):
3326
"""Translate any ErrorFromSmartServer values that don't require context"""
3327
if err.error_verb == 'IncompatibleRepositories':
3328
raise errors.IncompatibleRepositories(err.error_args[0],
3329
err.error_args[1], err.error_args[2])
3330
elif err.error_verb == 'LockContention':
3331
raise errors.LockContention('(remote lock)')
3332
elif err.error_verb == 'LockFailed':
3333
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3334
elif err.error_verb == 'TipChangeRejected':
3335
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3336
elif err.error_verb == 'UnstackableBranchFormat':
3337
raise errors.UnstackableBranchFormat(*err.error_args)
3338
elif err.error_verb == 'UnstackableRepositoryFormat':
3339
raise errors.UnstackableRepositoryFormat(*err.error_args)
3340
elif err.error_verb == 'FileExists':
3341
raise errors.FileExists(err.error_args[0])
3342
elif err.error_verb == 'DirectoryNotEmpty':
3343
raise errors.DirectoryNotEmpty(err.error_args[0])
3344
elif err.error_verb == 'ShortReadvError':
3345
args = err.error_args
3346
raise errors.ShortReadvError(
3347
args[0], int(args[1]), int(args[2]), int(args[3]))
3348
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3349
encoding = str(err.error_args[0]) # encoding must always be a string
3350
val = err.error_args[1]
3351
start = int(err.error_args[2])
3352
end = int(err.error_args[3])
3353
reason = str(err.error_args[4]) # reason must always be a string
3354
if val.startswith('u:'):
3355
val = val[2:].decode('utf-8')
3356
elif val.startswith('s:'):
3357
val = val[2:].decode('base64')
3358
if err.error_verb == 'UnicodeDecodeError':
3359
raise UnicodeDecodeError(encoding, val, start, end, reason)
3360
elif err.error_verb == 'UnicodeEncodeError':
3361
raise UnicodeEncodeError(encoding, val, start, end, reason)
3362
elif err.error_verb == 'ReadOnlyError':
3363
raise errors.TransportNotPossible('readonly transport')
3364
elif err.error_verb == 'MemoryError':
3365
raise errors.BzrError("remote server out of memory\n"
3366
"Retry non-remotely, or contact the server admin for details.")
3367
raise errors.UnknownErrorFromSmartServer(err)