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,
37
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
43
from bzrlib.i18n import gettext
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
_DEFAULT_SEARCH_DEPTH = 100
55
class _RpcHelper(object):
56
"""Mixin class that helps with issuing RPCs."""
58
def _call(self, method, *args, **err_context):
60
return self._client.call(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_expecting_body(self, method, *args, **err_context):
66
return self._client.call_expecting_body(method, *args)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
72
return self._client.call_with_body_bytes(method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
76
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
79
return self._client.call_with_body_bytes_expecting_body(
80
method, args, body_bytes)
81
except errors.ErrorFromSmartServer, err:
82
self._translate_error(err, **err_context)
85
def response_tuple_to_repo_format(response):
86
"""Convert a response tuple describing a repository format to a format."""
87
format = RemoteRepositoryFormat()
88
format._rich_root_data = (response[0] == 'yes')
89
format._supports_tree_reference = (response[1] == 'yes')
90
format._supports_external_lookups = (response[2] == 'yes')
91
format._network_name = response[3]
95
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
96
# does not have to be imported unless a remote format is involved.
98
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
99
"""Format representing bzrdirs accessed via a smart server"""
101
supports_workingtrees = False
104
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
105
# XXX: It's a bit ugly that the network name is here, because we'd
106
# like to believe that format objects are stateless or at least
107
# immutable, However, we do at least avoid mutating the name after
108
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
109
self._network_name = None
112
return "%s(_network_name=%r)" % (self.__class__.__name__,
115
def get_format_description(self):
116
if self._network_name:
117
real_format = controldir.network_format_registry.get(self._network_name)
118
return 'Remote: ' + real_format.get_format_description()
119
return 'bzr remote bzrdir'
121
def get_format_string(self):
122
raise NotImplementedError(self.get_format_string)
124
def network_name(self):
125
if self._network_name:
126
return self._network_name
128
raise AssertionError("No network name set.")
130
def initialize_on_transport(self, transport):
132
# hand off the request to the smart server
133
client_medium = transport.get_smart_medium()
134
except errors.NoSmartMedium:
135
# TODO: lookup the local format from a server hint.
136
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
137
return local_dir_format.initialize_on_transport(transport)
138
client = _SmartClient(client_medium)
139
path = client.remote_path_from_transport(transport)
141
response = client.call('BzrDirFormat.initialize', path)
142
except errors.ErrorFromSmartServer, err:
143
_translate_error(err, path=path)
144
if response[0] != 'ok':
145
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
146
format = RemoteBzrDirFormat()
147
self._supply_sub_formats_to(format)
148
return RemoteBzrDir(transport, format)
150
def parse_NoneTrueFalse(self, arg):
157
raise AssertionError("invalid arg %r" % arg)
159
def _serialize_NoneTrueFalse(self, arg):
166
def _serialize_NoneString(self, arg):
169
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
170
create_prefix=False, force_new_repo=False, stacked_on=None,
171
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
174
# hand off the request to the smart server
175
client_medium = transport.get_smart_medium()
176
except errors.NoSmartMedium:
179
# Decline to open it if the server doesn't support our required
180
# version (3) so that the VFS-based transport will do it.
181
if client_medium.should_probe():
183
server_version = client_medium.protocol_version()
184
if server_version != '2':
188
except errors.SmartProtocolError:
189
# Apparently there's no usable smart server there, even though
190
# the medium supports the smart protocol.
195
client = _SmartClient(client_medium)
196
path = client.remote_path_from_transport(transport)
197
if client_medium._is_remote_before((1, 16)):
200
# TODO: lookup the local format from a server hint.
201
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
202
self._supply_sub_formats_to(local_dir_format)
203
return local_dir_format.initialize_on_transport_ex(transport,
204
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
205
force_new_repo=force_new_repo, stacked_on=stacked_on,
206
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
207
make_working_trees=make_working_trees, shared_repo=shared_repo,
209
return self._initialize_on_transport_ex_rpc(client, path, transport,
210
use_existing_dir, create_prefix, force_new_repo, stacked_on,
211
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
213
def _initialize_on_transport_ex_rpc(self, client, path, transport,
214
use_existing_dir, create_prefix, force_new_repo, stacked_on,
215
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
217
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
218
args.append(self._serialize_NoneTrueFalse(create_prefix))
219
args.append(self._serialize_NoneTrueFalse(force_new_repo))
220
args.append(self._serialize_NoneString(stacked_on))
221
# stack_on_pwd is often/usually our transport
224
stack_on_pwd = transport.relpath(stack_on_pwd)
227
except errors.PathNotChild:
229
args.append(self._serialize_NoneString(stack_on_pwd))
230
args.append(self._serialize_NoneString(repo_format_name))
231
args.append(self._serialize_NoneTrueFalse(make_working_trees))
232
args.append(self._serialize_NoneTrueFalse(shared_repo))
233
request_network_name = self._network_name or \
234
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
236
response = client.call('BzrDirFormat.initialize_ex_1.16',
237
request_network_name, path, *args)
238
except errors.UnknownSmartMethod:
239
client._medium._remember_remote_is_before((1,16))
240
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
241
self._supply_sub_formats_to(local_dir_format)
242
return local_dir_format.initialize_on_transport_ex(transport,
243
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
244
force_new_repo=force_new_repo, stacked_on=stacked_on,
245
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
246
make_working_trees=make_working_trees, shared_repo=shared_repo,
248
except errors.ErrorFromSmartServer, err:
249
_translate_error(err, path=path)
250
repo_path = response[0]
251
bzrdir_name = response[6]
252
require_stacking = response[7]
253
require_stacking = self.parse_NoneTrueFalse(require_stacking)
254
format = RemoteBzrDirFormat()
255
format._network_name = bzrdir_name
256
self._supply_sub_formats_to(format)
257
bzrdir = RemoteBzrDir(transport, format, _client=client)
259
repo_format = response_tuple_to_repo_format(response[1:])
263
repo_bzrdir_format = RemoteBzrDirFormat()
264
repo_bzrdir_format._network_name = response[5]
265
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
269
final_stack = response[8] or None
270
final_stack_pwd = response[9] or None
272
final_stack_pwd = urlutils.join(
273
transport.base, final_stack_pwd)
274
remote_repo = RemoteRepository(repo_bzr, repo_format)
275
if len(response) > 10:
276
# Updated server verb that locks remotely.
277
repo_lock_token = response[10] or None
278
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
280
remote_repo.dont_leave_lock_in_place()
282
remote_repo.lock_write()
283
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
284
final_stack_pwd, require_stacking)
285
policy.acquire_repository()
289
bzrdir._format.set_branch_format(self.get_branch_format())
291
# The repo has already been created, but we need to make sure that
292
# we'll make a stackable branch.
293
bzrdir._format.require_stacking(_skip_repo=True)
294
return remote_repo, bzrdir, require_stacking, policy
296
def _open(self, transport):
297
return RemoteBzrDir(transport, self)
299
def __eq__(self, other):
300
if not isinstance(other, RemoteBzrDirFormat):
302
return self.get_format_description() == other.get_format_description()
304
def __return_repository_format(self):
305
# Always return a RemoteRepositoryFormat object, but if a specific bzr
306
# repository format has been asked for, tell the RemoteRepositoryFormat
307
# that it should use that for init() etc.
308
result = RemoteRepositoryFormat()
309
custom_format = getattr(self, '_repository_format', None)
311
if isinstance(custom_format, RemoteRepositoryFormat):
314
# We will use the custom format to create repositories over the
315
# wire; expose its details like rich_root_data for code to
317
result._custom_format = custom_format
320
def get_branch_format(self):
321
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
322
if not isinstance(result, RemoteBranchFormat):
323
new_result = RemoteBranchFormat()
324
new_result._custom_format = result
326
self.set_branch_format(new_result)
330
repository_format = property(__return_repository_format,
331
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
334
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
335
"""Control directory on a remote server, accessed via bzr:// or similar."""
337
def __init__(self, transport, format, _client=None, _force_probe=False):
338
"""Construct a RemoteBzrDir.
340
:param _client: Private parameter for testing. Disables probing and the
341
use of a real bzrdir.
343
_mod_bzrdir.BzrDir.__init__(self, transport, format)
344
# this object holds a delegated bzrdir that uses file-level operations
345
# to talk to the other side
346
self._real_bzrdir = None
347
self._has_working_tree = None
348
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
349
# create_branch for details.
350
self._next_open_branch_result = None
353
medium = transport.get_smart_medium()
354
self._client = client._SmartClient(medium)
356
self._client = _client
363
return '%s(%r)' % (self.__class__.__name__, self._client)
365
def _probe_bzrdir(self):
366
medium = self._client._medium
367
path = self._path_for_remote_call(self._client)
368
if medium._is_remote_before((2, 1)):
372
self._rpc_open_2_1(path)
374
except errors.UnknownSmartMethod:
375
medium._remember_remote_is_before((2, 1))
378
def _rpc_open_2_1(self, path):
379
response = self._call('BzrDir.open_2.1', path)
380
if response == ('no',):
381
raise errors.NotBranchError(path=self.root_transport.base)
382
elif response[0] == 'yes':
383
if response[1] == 'yes':
384
self._has_working_tree = True
385
elif response[1] == 'no':
386
self._has_working_tree = False
388
raise errors.UnexpectedSmartServerResponse(response)
390
raise errors.UnexpectedSmartServerResponse(response)
392
def _rpc_open(self, path):
393
response = self._call('BzrDir.open', path)
394
if response not in [('yes',), ('no',)]:
395
raise errors.UnexpectedSmartServerResponse(response)
396
if response == ('no',):
397
raise errors.NotBranchError(path=self.root_transport.base)
399
def _ensure_real(self):
400
"""Ensure that there is a _real_bzrdir set.
402
Used before calls to self._real_bzrdir.
404
if not self._real_bzrdir:
405
if 'hpssvfs' in debug.debug_flags:
407
warning('VFS BzrDir access triggered\n%s',
408
''.join(traceback.format_stack()))
409
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
410
self.root_transport, _server_formats=False)
411
self._format._network_name = \
412
self._real_bzrdir._format.network_name()
414
def _translate_error(self, err, **context):
415
_translate_error(err, bzrdir=self, **context)
417
def break_lock(self):
418
# Prevent aliasing problems in the next_open_branch_result cache.
419
# See create_branch for rationale.
420
self._next_open_branch_result = None
421
return _mod_bzrdir.BzrDir.break_lock(self)
423
def _vfs_cloning_metadir(self, require_stacking=False):
425
return self._real_bzrdir.cloning_metadir(
426
require_stacking=require_stacking)
428
def cloning_metadir(self, require_stacking=False):
429
medium = self._client._medium
430
if medium._is_remote_before((1, 13)):
431
return self._vfs_cloning_metadir(require_stacking=require_stacking)
432
verb = 'BzrDir.cloning_metadir'
437
path = self._path_for_remote_call(self._client)
439
response = self._call(verb, path, stacking)
440
except errors.UnknownSmartMethod:
441
medium._remember_remote_is_before((1, 13))
442
return self._vfs_cloning_metadir(require_stacking=require_stacking)
443
except errors.UnknownErrorFromSmartServer, err:
444
if err.error_tuple != ('BranchReference',):
446
# We need to resolve the branch reference to determine the
447
# cloning_metadir. This causes unnecessary RPCs to open the
448
# referenced branch (and bzrdir, etc) but only when the caller
449
# didn't already resolve the branch reference.
450
referenced_branch = self.open_branch()
451
return referenced_branch.bzrdir.cloning_metadir()
452
if len(response) != 3:
453
raise errors.UnexpectedSmartServerResponse(response)
454
control_name, repo_name, branch_info = response
455
if len(branch_info) != 2:
456
raise errors.UnexpectedSmartServerResponse(response)
457
branch_ref, branch_name = branch_info
458
format = controldir.network_format_registry.get(control_name)
460
format.repository_format = _mod_repository.network_format_registry.get(
462
if branch_ref == 'ref':
463
# XXX: we need possible_transports here to avoid reopening the
464
# connection to the referenced location
465
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
466
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
467
format.set_branch_format(branch_format)
468
elif branch_ref == 'branch':
470
format.set_branch_format(
471
branch.network_format_registry.get(branch_name))
473
raise errors.UnexpectedSmartServerResponse(response)
476
def create_repository(self, shared=False):
477
# as per meta1 formats - just delegate to the format object which may
479
result = self._format.repository_format.initialize(self, shared)
480
if not isinstance(result, RemoteRepository):
481
return self.open_repository()
485
def destroy_repository(self):
486
"""See BzrDir.destroy_repository"""
488
self._real_bzrdir.destroy_repository()
490
def create_branch(self, name=None, repository=None,
491
append_revisions_only=None):
492
# as per meta1 formats - just delegate to the format object which may
494
real_branch = self._format.get_branch_format().initialize(self,
495
name=name, repository=repository,
496
append_revisions_only=append_revisions_only)
497
if not isinstance(real_branch, RemoteBranch):
498
if not isinstance(repository, RemoteRepository):
499
raise AssertionError(
500
'need a RemoteRepository to use with RemoteBranch, got %r'
502
result = RemoteBranch(self, repository, real_branch, name=name)
505
# BzrDir.clone_on_transport() uses the result of create_branch but does
506
# not return it to its callers; we save approximately 8% of our round
507
# trips by handing the branch we created back to the first caller to
508
# open_branch rather than probing anew. Long term we need a API in
509
# bzrdir that doesn't discard result objects (like result_branch).
511
self._next_open_branch_result = result
514
def destroy_branch(self, name=None):
515
"""See BzrDir.destroy_branch"""
517
self._real_bzrdir.destroy_branch(name=name)
518
self._next_open_branch_result = None
520
def create_workingtree(self, revision_id=None, from_branch=None,
521
accelerator_tree=None, hardlink=False):
522
raise errors.NotLocalUrl(self.transport.base)
524
def find_branch_format(self, name=None):
525
"""Find the branch 'format' for this bzrdir.
527
This might be a synthetic object for e.g. RemoteBranch and SVN.
529
b = self.open_branch(name=name)
532
def get_branch_reference(self, name=None):
533
"""See BzrDir.get_branch_reference()."""
535
# XXX JRV20100304: Support opening colocated branches
536
raise errors.NoColocatedBranchSupport(self)
537
response = self._get_branch_reference()
538
if response[0] == 'ref':
543
def _get_branch_reference(self):
544
path = self._path_for_remote_call(self._client)
545
medium = self._client._medium
547
('BzrDir.open_branchV3', (2, 1)),
548
('BzrDir.open_branchV2', (1, 13)),
549
('BzrDir.open_branch', None),
551
for verb, required_version in candidate_calls:
552
if required_version and medium._is_remote_before(required_version):
555
response = self._call(verb, path)
556
except errors.UnknownSmartMethod:
557
if required_version is None:
559
medium._remember_remote_is_before(required_version)
562
if verb == 'BzrDir.open_branch':
563
if response[0] != 'ok':
564
raise errors.UnexpectedSmartServerResponse(response)
565
if response[1] != '':
566
return ('ref', response[1])
568
return ('branch', '')
569
if response[0] not in ('ref', 'branch'):
570
raise errors.UnexpectedSmartServerResponse(response)
573
def _get_tree_branch(self, name=None):
574
"""See BzrDir._get_tree_branch()."""
575
return None, self.open_branch(name=name)
577
def open_branch(self, name=None, unsupported=False,
578
ignore_fallbacks=False):
580
raise NotImplementedError('unsupported flag support not implemented yet.')
581
if self._next_open_branch_result is not None:
582
# See create_branch for details.
583
result = self._next_open_branch_result
584
self._next_open_branch_result = None
586
response = self._get_branch_reference()
587
if response[0] == 'ref':
588
# a branch reference, use the existing BranchReference logic.
589
format = BranchReferenceFormat()
590
return format.open(self, name=name, _found=True,
591
location=response[1], ignore_fallbacks=ignore_fallbacks)
592
branch_format_name = response[1]
593
if not branch_format_name:
594
branch_format_name = None
595
format = RemoteBranchFormat(network_name=branch_format_name)
596
return RemoteBranch(self, self.find_repository(), format=format,
597
setup_stacking=not ignore_fallbacks, name=name)
599
def _open_repo_v1(self, path):
600
verb = 'BzrDir.find_repository'
601
response = self._call(verb, path)
602
if response[0] != 'ok':
603
raise errors.UnexpectedSmartServerResponse(response)
604
# servers that only support the v1 method don't support external
607
repo = self._real_bzrdir.open_repository()
608
response = response + ('no', repo._format.network_name())
609
return response, repo
611
def _open_repo_v2(self, path):
612
verb = 'BzrDir.find_repositoryV2'
613
response = self._call(verb, path)
614
if response[0] != 'ok':
615
raise errors.UnexpectedSmartServerResponse(response)
617
repo = self._real_bzrdir.open_repository()
618
response = response + (repo._format.network_name(),)
619
return response, repo
621
def _open_repo_v3(self, path):
622
verb = 'BzrDir.find_repositoryV3'
623
medium = self._client._medium
624
if medium._is_remote_before((1, 13)):
625
raise errors.UnknownSmartMethod(verb)
627
response = self._call(verb, path)
628
except errors.UnknownSmartMethod:
629
medium._remember_remote_is_before((1, 13))
631
if response[0] != 'ok':
632
raise errors.UnexpectedSmartServerResponse(response)
633
return response, None
635
def open_repository(self):
636
path = self._path_for_remote_call(self._client)
638
for probe in [self._open_repo_v3, self._open_repo_v2,
641
response, real_repo = probe(path)
643
except errors.UnknownSmartMethod:
646
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
647
if response[0] != 'ok':
648
raise errors.UnexpectedSmartServerResponse(response)
649
if len(response) != 6:
650
raise SmartProtocolError('incorrect response length %s' % (response,))
651
if response[1] == '':
652
# repo is at this dir.
653
format = response_tuple_to_repo_format(response[2:])
654
# Used to support creating a real format instance when needed.
655
format._creating_bzrdir = self
656
remote_repo = RemoteRepository(self, format)
657
format._creating_repo = remote_repo
658
if real_repo is not None:
659
remote_repo._set_real_repository(real_repo)
662
raise errors.NoRepositoryPresent(self)
664
def has_workingtree(self):
665
if self._has_working_tree is None:
667
self._has_working_tree = self._real_bzrdir.has_workingtree()
668
return self._has_working_tree
670
def open_workingtree(self, recommend_upgrade=True):
671
if self.has_workingtree():
672
raise errors.NotLocalUrl(self.root_transport)
674
raise errors.NoWorkingTree(self.root_transport.base)
676
def _path_for_remote_call(self, client):
677
"""Return the path to be used for this bzrdir in a remote call."""
678
return urlutils.split_segment_parameters_raw(
679
client.remote_path_from_transport(self.root_transport))[0]
681
def get_branch_transport(self, branch_format, name=None):
683
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
685
def get_repository_transport(self, repository_format):
687
return self._real_bzrdir.get_repository_transport(repository_format)
689
def get_workingtree_transport(self, workingtree_format):
691
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
693
def can_convert_format(self):
694
"""Upgrading of remote bzrdirs is not supported yet."""
697
def needs_format_conversion(self, format):
698
"""Upgrading of remote bzrdirs is not supported yet."""
701
def clone(self, url, revision_id=None, force_new_repo=False,
702
preserve_stacking=False):
704
return self._real_bzrdir.clone(url, revision_id=revision_id,
705
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
707
def _get_config(self):
708
return RemoteBzrDirConfig(self)
711
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
712
"""Format for repositories accessed over a _SmartClient.
714
Instances of this repository are represented by RemoteRepository
717
The RemoteRepositoryFormat is parameterized during construction
718
to reflect the capabilities of the real, remote format. Specifically
719
the attributes rich_root_data and supports_tree_reference are set
720
on a per instance basis, and are not set (and should not be) at
723
:ivar _custom_format: If set, a specific concrete repository format that
724
will be used when initializing a repository with this
725
RemoteRepositoryFormat.
726
:ivar _creating_repo: If set, the repository object that this
727
RemoteRepositoryFormat was created for: it can be called into
728
to obtain data like the network name.
731
_matchingbzrdir = RemoteBzrDirFormat()
732
supports_full_versioned_files = True
733
supports_leaving_lock = True
736
_mod_repository.RepositoryFormat.__init__(self)
737
self._custom_format = None
738
self._network_name = None
739
self._creating_bzrdir = None
740
self._revision_graph_can_have_wrong_parents = None
741
self._supports_chks = None
742
self._supports_external_lookups = None
743
self._supports_tree_reference = None
744
self._supports_funky_characters = None
745
self._supports_nesting_repositories = None
746
self._rich_root_data = None
749
return "%s(_network_name=%r)" % (self.__class__.__name__,
753
def fast_deltas(self):
755
return self._custom_format.fast_deltas
758
def rich_root_data(self):
759
if self._rich_root_data is None:
761
self._rich_root_data = self._custom_format.rich_root_data
762
return self._rich_root_data
765
def supports_chks(self):
766
if self._supports_chks is None:
768
self._supports_chks = self._custom_format.supports_chks
769
return self._supports_chks
772
def supports_external_lookups(self):
773
if self._supports_external_lookups is None:
775
self._supports_external_lookups = \
776
self._custom_format.supports_external_lookups
777
return self._supports_external_lookups
780
def supports_funky_characters(self):
781
if self._supports_funky_characters is None:
783
self._supports_funky_characters = \
784
self._custom_format.supports_funky_characters
785
return self._supports_funky_characters
788
def supports_nesting_repositories(self):
789
if self._supports_nesting_repositories is None:
791
self._supports_nesting_repositories = \
792
self._custom_format.supports_nesting_repositories
793
return self._supports_nesting_repositories
796
def supports_tree_reference(self):
797
if self._supports_tree_reference is None:
799
self._supports_tree_reference = \
800
self._custom_format.supports_tree_reference
801
return self._supports_tree_reference
804
def revision_graph_can_have_wrong_parents(self):
805
if self._revision_graph_can_have_wrong_parents is None:
807
self._revision_graph_can_have_wrong_parents = \
808
self._custom_format.revision_graph_can_have_wrong_parents
809
return self._revision_graph_can_have_wrong_parents
811
def _vfs_initialize(self, a_bzrdir, shared):
812
"""Helper for common code in initialize."""
813
if self._custom_format:
814
# Custom format requested
815
result = self._custom_format.initialize(a_bzrdir, shared=shared)
816
elif self._creating_bzrdir is not None:
817
# Use the format that the repository we were created to back
819
prior_repo = self._creating_bzrdir.open_repository()
820
prior_repo._ensure_real()
821
result = prior_repo._real_repository._format.initialize(
822
a_bzrdir, shared=shared)
824
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
825
# support remote initialization.
826
# We delegate to a real object at this point (as RemoteBzrDir
827
# delegate to the repository format which would lead to infinite
828
# recursion if we just called a_bzrdir.create_repository.
829
a_bzrdir._ensure_real()
830
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
831
if not isinstance(result, RemoteRepository):
832
return self.open(a_bzrdir)
836
def initialize(self, a_bzrdir, shared=False):
837
# Being asked to create on a non RemoteBzrDir:
838
if not isinstance(a_bzrdir, RemoteBzrDir):
839
return self._vfs_initialize(a_bzrdir, shared)
840
medium = a_bzrdir._client._medium
841
if medium._is_remote_before((1, 13)):
842
return self._vfs_initialize(a_bzrdir, shared)
843
# Creating on a remote bzr dir.
844
# 1) get the network name to use.
845
if self._custom_format:
846
network_name = self._custom_format.network_name()
847
elif self._network_name:
848
network_name = self._network_name
850
# Select the current bzrlib default and ask for that.
851
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
852
reference_format = reference_bzrdir_format.repository_format
853
network_name = reference_format.network_name()
854
# 2) try direct creation via RPC
855
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
856
verb = 'BzrDir.create_repository'
862
response = a_bzrdir._call(verb, path, network_name, shared_str)
863
except errors.UnknownSmartMethod:
864
# Fallback - use vfs methods
865
medium._remember_remote_is_before((1, 13))
866
return self._vfs_initialize(a_bzrdir, shared)
868
# Turn the response into a RemoteRepository object.
869
format = response_tuple_to_repo_format(response[1:])
870
# Used to support creating a real format instance when needed.
871
format._creating_bzrdir = a_bzrdir
872
remote_repo = RemoteRepository(a_bzrdir, format)
873
format._creating_repo = remote_repo
876
def open(self, a_bzrdir):
877
if not isinstance(a_bzrdir, RemoteBzrDir):
878
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
879
return a_bzrdir.open_repository()
881
def _ensure_real(self):
882
if self._custom_format is None:
883
self._custom_format = _mod_repository.network_format_registry.get(
887
def _fetch_order(self):
889
return self._custom_format._fetch_order
892
def _fetch_uses_deltas(self):
894
return self._custom_format._fetch_uses_deltas
897
def _fetch_reconcile(self):
899
return self._custom_format._fetch_reconcile
901
def get_format_description(self):
903
return 'Remote: ' + self._custom_format.get_format_description()
905
def __eq__(self, other):
906
return self.__class__ is other.__class__
908
def network_name(self):
909
if self._network_name:
910
return self._network_name
911
self._creating_repo._ensure_real()
912
return self._creating_repo._real_repository._format.network_name()
915
def pack_compresses(self):
917
return self._custom_format.pack_compresses
920
def _serializer(self):
922
return self._custom_format._serializer
925
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
926
controldir.ControlComponent):
927
"""Repository accessed over rpc.
929
For the moment most operations are performed using local transport-backed
933
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
934
"""Create a RemoteRepository instance.
936
:param remote_bzrdir: The bzrdir hosting this repository.
937
:param format: The RemoteFormat object to use.
938
:param real_repository: If not None, a local implementation of the
939
repository logic for the repository, usually accessing the data
941
:param _client: Private testing parameter - override the smart client
942
to be used by the repository.
945
self._real_repository = real_repository
947
self._real_repository = None
948
self.bzrdir = remote_bzrdir
950
self._client = remote_bzrdir._client
952
self._client = _client
953
self._format = format
954
self._lock_mode = None
955
self._lock_token = None
957
self._leave_lock = False
958
# Cache of revision parents; misses are cached during read locks, and
959
# write locks when no _real_repository has been set.
960
self._unstacked_provider = graph.CachingParentsProvider(
961
get_parent_map=self._get_parent_map_rpc)
962
self._unstacked_provider.disable_cache()
964
# These depend on the actual remote format, so force them off for
965
# maximum compatibility. XXX: In future these should depend on the
966
# remote repository instance, but this is irrelevant until we perform
967
# reconcile via an RPC call.
968
self._reconcile_does_inventory_gc = False
969
self._reconcile_fixes_text_parents = False
970
self._reconcile_backsup_inventory = False
971
self.base = self.bzrdir.transport.base
972
# Additional places to query for data.
973
self._fallback_repositories = []
976
def user_transport(self):
977
return self.bzrdir.user_transport
980
def control_transport(self):
981
# XXX: Normally you shouldn't directly get at the remote repository
982
# transport, but I'm not sure it's worth making this method
983
# optional -- mbp 2010-04-21
984
return self.bzrdir.get_repository_transport(None)
987
return "%s(%s)" % (self.__class__.__name__, self.base)
991
def abort_write_group(self, suppress_errors=False):
992
"""Complete a write group on the decorated repository.
994
Smart methods perform operations in a single step so this API
995
is not really applicable except as a compatibility thunk
996
for older plugins that don't use e.g. the CommitBuilder
999
:param suppress_errors: see Repository.abort_write_group.
1002
return self._real_repository.abort_write_group(
1003
suppress_errors=suppress_errors)
1006
def chk_bytes(self):
1007
"""Decorate the real repository for now.
1009
In the long term a full blown network facility is needed to avoid
1010
creating a real repository object locally.
1013
return self._real_repository.chk_bytes
1015
def commit_write_group(self):
1016
"""Complete a write group on the decorated repository.
1018
Smart methods perform operations in a single step so this API
1019
is not really applicable except as a compatibility thunk
1020
for older plugins that don't use e.g. the CommitBuilder
1024
return self._real_repository.commit_write_group()
1026
def resume_write_group(self, tokens):
1028
return self._real_repository.resume_write_group(tokens)
1030
def suspend_write_group(self):
1032
return self._real_repository.suspend_write_group()
1034
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1036
return self._real_repository.get_missing_parent_inventories(
1037
check_for_missing_texts=check_for_missing_texts)
1039
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1041
return self._real_repository.get_rev_id_for_revno(
1044
def get_rev_id_for_revno(self, revno, known_pair):
1045
"""See Repository.get_rev_id_for_revno."""
1046
path = self.bzrdir._path_for_remote_call(self._client)
1048
if self._client._medium._is_remote_before((1, 17)):
1049
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1050
response = self._call(
1051
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1052
except errors.UnknownSmartMethod:
1053
self._client._medium._remember_remote_is_before((1, 17))
1054
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1055
if response[0] == 'ok':
1056
return True, response[1]
1057
elif response[0] == 'history-incomplete':
1058
known_pair = response[1:3]
1059
for fallback in self._fallback_repositories:
1060
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1065
# Not found in any fallbacks
1066
return False, known_pair
1068
raise errors.UnexpectedSmartServerResponse(response)
1070
def _ensure_real(self):
1071
"""Ensure that there is a _real_repository set.
1073
Used before calls to self._real_repository.
1075
Note that _ensure_real causes many roundtrips to the server which are
1076
not desirable, and prevents the use of smart one-roundtrip RPC's to
1077
perform complex operations (such as accessing parent data, streaming
1078
revisions etc). Adding calls to _ensure_real should only be done when
1079
bringing up new functionality, adding fallbacks for smart methods that
1080
require a fallback path, and never to replace an existing smart method
1081
invocation. If in doubt chat to the bzr network team.
1083
if self._real_repository is None:
1084
if 'hpssvfs' in debug.debug_flags:
1086
warning('VFS Repository access triggered\n%s',
1087
''.join(traceback.format_stack()))
1088
self._unstacked_provider.missing_keys.clear()
1089
self.bzrdir._ensure_real()
1090
self._set_real_repository(
1091
self.bzrdir._real_bzrdir.open_repository())
1093
def _translate_error(self, err, **context):
1094
self.bzrdir._translate_error(err, repository=self, **context)
1096
def find_text_key_references(self):
1097
"""Find the text key references within the repository.
1099
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1100
to whether they were referred to by the inventory of the
1101
revision_id that they contain. The inventory texts from all present
1102
revision ids are assessed to generate this report.
1105
return self._real_repository.find_text_key_references()
1107
def _generate_text_key_index(self):
1108
"""Generate a new text key index for the repository.
1110
This is an expensive function that will take considerable time to run.
1112
:return: A dict mapping (file_id, revision_id) tuples to a list of
1113
parents, also (file_id, revision_id) tuples.
1116
return self._real_repository._generate_text_key_index()
1118
def _get_revision_graph(self, revision_id):
1119
"""Private method for using with old (< 1.2) servers to fallback."""
1120
if revision_id is None:
1122
elif _mod_revision.is_null(revision_id):
1125
path = self.bzrdir._path_for_remote_call(self._client)
1126
response = self._call_expecting_body(
1127
'Repository.get_revision_graph', path, revision_id)
1128
response_tuple, response_handler = response
1129
if response_tuple[0] != 'ok':
1130
raise errors.UnexpectedSmartServerResponse(response_tuple)
1131
coded = response_handler.read_body_bytes()
1133
# no revisions in this repository!
1135
lines = coded.split('\n')
1138
d = tuple(line.split())
1139
revision_graph[d[0]] = d[1:]
1141
return revision_graph
1143
def _get_sink(self):
1144
"""See Repository._get_sink()."""
1145
return RemoteStreamSink(self)
1147
def _get_source(self, to_format):
1148
"""Return a source for streaming from this repository."""
1149
return RemoteStreamSource(self, to_format)
1152
def get_file_graph(self):
1153
return graph.Graph(self.texts)
1156
def has_revision(self, revision_id):
1157
"""True if this repository has a copy of the revision."""
1158
# Copy of bzrlib.repository.Repository.has_revision
1159
return revision_id in self.has_revisions((revision_id,))
1162
def has_revisions(self, revision_ids):
1163
"""Probe to find out the presence of multiple revisions.
1165
:param revision_ids: An iterable of revision_ids.
1166
:return: A set of the revision_ids that were present.
1168
# Copy of bzrlib.repository.Repository.has_revisions
1169
parent_map = self.get_parent_map(revision_ids)
1170
result = set(parent_map)
1171
if _mod_revision.NULL_REVISION in revision_ids:
1172
result.add(_mod_revision.NULL_REVISION)
1175
def _has_same_fallbacks(self, other_repo):
1176
"""Returns true if the repositories have the same fallbacks."""
1177
# XXX: copied from Repository; it should be unified into a base class
1178
# <https://bugs.launchpad.net/bzr/+bug/401622>
1179
my_fb = self._fallback_repositories
1180
other_fb = other_repo._fallback_repositories
1181
if len(my_fb) != len(other_fb):
1183
for f, g in zip(my_fb, other_fb):
1184
if not f.has_same_location(g):
1188
def has_same_location(self, other):
1189
# TODO: Move to RepositoryBase and unify with the regular Repository
1190
# one; unfortunately the tests rely on slightly different behaviour at
1191
# present -- mbp 20090710
1192
return (self.__class__ is other.__class__ and
1193
self.bzrdir.transport.base == other.bzrdir.transport.base)
1195
def get_graph(self, other_repository=None):
1196
"""Return the graph for this repository format"""
1197
parents_provider = self._make_parents_provider(other_repository)
1198
return graph.Graph(parents_provider)
1201
def get_known_graph_ancestry(self, revision_ids):
1202
"""Return the known graph for a set of revision ids and their ancestors.
1204
st = static_tuple.StaticTuple
1205
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1206
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1207
return graph.GraphThunkIdsToKeys(known_graph)
1209
def gather_stats(self, revid=None, committers=None):
1210
"""See Repository.gather_stats()."""
1211
path = self.bzrdir._path_for_remote_call(self._client)
1212
# revid can be None to indicate no revisions, not just NULL_REVISION
1213
if revid is None or _mod_revision.is_null(revid):
1217
if committers is None or not committers:
1218
fmt_committers = 'no'
1220
fmt_committers = 'yes'
1221
response_tuple, response_handler = self._call_expecting_body(
1222
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1223
if response_tuple[0] != 'ok':
1224
raise errors.UnexpectedSmartServerResponse(response_tuple)
1226
body = response_handler.read_body_bytes()
1228
for line in body.split('\n'):
1231
key, val_text = line.split(':')
1232
if key in ('revisions', 'size', 'committers'):
1233
result[key] = int(val_text)
1234
elif key in ('firstrev', 'latestrev'):
1235
values = val_text.split(' ')[1:]
1236
result[key] = (float(values[0]), long(values[1]))
1240
def find_branches(self, using=False):
1241
"""See Repository.find_branches()."""
1242
# should be an API call to the server.
1244
return self._real_repository.find_branches(using=using)
1246
def get_physical_lock_status(self):
1247
"""See Repository.get_physical_lock_status()."""
1248
# should be an API call to the server.
1250
return self._real_repository.get_physical_lock_status()
1252
def is_in_write_group(self):
1253
"""Return True if there is an open write group.
1255
write groups are only applicable locally for the smart server..
1257
if self._real_repository:
1258
return self._real_repository.is_in_write_group()
1260
def is_locked(self):
1261
return self._lock_count >= 1
1263
def is_shared(self):
1264
"""See Repository.is_shared()."""
1265
path = self.bzrdir._path_for_remote_call(self._client)
1266
response = self._call('Repository.is_shared', path)
1267
if response[0] not in ('yes', 'no'):
1268
raise SmartProtocolError('unexpected response code %s' % (response,))
1269
return response[0] == 'yes'
1271
def is_write_locked(self):
1272
return self._lock_mode == 'w'
1274
def _warn_if_deprecated(self, branch=None):
1275
# If we have a real repository, the check will be done there, if we
1276
# don't the check will be done remotely.
1279
def lock_read(self):
1280
"""Lock the repository for read operations.
1282
:return: A bzrlib.lock.LogicalLockResult.
1284
# wrong eventually - want a local lock cache context
1285
if not self._lock_mode:
1286
self._note_lock('r')
1287
self._lock_mode = 'r'
1288
self._lock_count = 1
1289
self._unstacked_provider.enable_cache(cache_misses=True)
1290
if self._real_repository is not None:
1291
self._real_repository.lock_read()
1292
for repo in self._fallback_repositories:
1295
self._lock_count += 1
1296
return lock.LogicalLockResult(self.unlock)
1298
def _remote_lock_write(self, token):
1299
path = self.bzrdir._path_for_remote_call(self._client)
1302
err_context = {'token': token}
1303
response = self._call('Repository.lock_write', path, token,
1305
if response[0] == 'ok':
1306
ok, token = response
1309
raise errors.UnexpectedSmartServerResponse(response)
1311
def lock_write(self, token=None, _skip_rpc=False):
1312
if not self._lock_mode:
1313
self._note_lock('w')
1315
if self._lock_token is not None:
1316
if token != self._lock_token:
1317
raise errors.TokenMismatch(token, self._lock_token)
1318
self._lock_token = token
1320
self._lock_token = self._remote_lock_write(token)
1321
# if self._lock_token is None, then this is something like packs or
1322
# svn where we don't get to lock the repo, or a weave style repository
1323
# where we cannot lock it over the wire and attempts to do so will
1325
if self._real_repository is not None:
1326
self._real_repository.lock_write(token=self._lock_token)
1327
if token is not None:
1328
self._leave_lock = True
1330
self._leave_lock = False
1331
self._lock_mode = 'w'
1332
self._lock_count = 1
1333
cache_misses = self._real_repository is None
1334
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1335
for repo in self._fallback_repositories:
1336
# Writes don't affect fallback repos
1338
elif self._lock_mode == 'r':
1339
raise errors.ReadOnlyError(self)
1341
self._lock_count += 1
1342
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1344
def leave_lock_in_place(self):
1345
if not self._lock_token:
1346
raise NotImplementedError(self.leave_lock_in_place)
1347
self._leave_lock = True
1349
def dont_leave_lock_in_place(self):
1350
if not self._lock_token:
1351
raise NotImplementedError(self.dont_leave_lock_in_place)
1352
self._leave_lock = False
1354
def _set_real_repository(self, repository):
1355
"""Set the _real_repository for this repository.
1357
:param repository: The repository to fallback to for non-hpss
1358
implemented operations.
1360
if self._real_repository is not None:
1361
# Replacing an already set real repository.
1362
# We cannot do this [currently] if the repository is locked -
1363
# synchronised state might be lost.
1364
if self.is_locked():
1365
raise AssertionError('_real_repository is already set')
1366
if isinstance(repository, RemoteRepository):
1367
raise AssertionError()
1368
self._real_repository = repository
1369
# three code paths happen here:
1370
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1371
# up stacking. In this case self._fallback_repositories is [], and the
1372
# real repo is already setup. Preserve the real repo and
1373
# RemoteRepository.add_fallback_repository will avoid adding
1375
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1376
# ensure_real is triggered from a branch, the real repository to
1377
# set already has a matching list with separate instances, but
1378
# as they are also RemoteRepositories we don't worry about making the
1379
# lists be identical.
1380
# 3) new servers, RemoteRepository.ensure_real is triggered before
1381
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1382
# and need to populate it.
1383
if (self._fallback_repositories and
1384
len(self._real_repository._fallback_repositories) !=
1385
len(self._fallback_repositories)):
1386
if len(self._real_repository._fallback_repositories):
1387
raise AssertionError(
1388
"cannot cleanly remove existing _fallback_repositories")
1389
for fb in self._fallback_repositories:
1390
self._real_repository.add_fallback_repository(fb)
1391
if self._lock_mode == 'w':
1392
# if we are already locked, the real repository must be able to
1393
# acquire the lock with our token.
1394
self._real_repository.lock_write(self._lock_token)
1395
elif self._lock_mode == 'r':
1396
self._real_repository.lock_read()
1398
def start_write_group(self):
1399
"""Start a write group on the decorated repository.
1401
Smart methods perform operations in a single step so this API
1402
is not really applicable except as a compatibility thunk
1403
for older plugins that don't use e.g. the CommitBuilder
1407
return self._real_repository.start_write_group()
1409
def _unlock(self, token):
1410
path = self.bzrdir._path_for_remote_call(self._client)
1412
# with no token the remote repository is not persistently locked.
1414
err_context = {'token': token}
1415
response = self._call('Repository.unlock', path, token,
1417
if response == ('ok',):
1420
raise errors.UnexpectedSmartServerResponse(response)
1422
@only_raises(errors.LockNotHeld, errors.LockBroken)
1424
if not self._lock_count:
1425
return lock.cant_unlock_not_held(self)
1426
self._lock_count -= 1
1427
if self._lock_count > 0:
1429
self._unstacked_provider.disable_cache()
1430
old_mode = self._lock_mode
1431
self._lock_mode = None
1433
# The real repository is responsible at present for raising an
1434
# exception if it's in an unfinished write group. However, it
1435
# normally will *not* actually remove the lock from disk - that's
1436
# done by the server on receiving the Repository.unlock call.
1437
# This is just to let the _real_repository stay up to date.
1438
if self._real_repository is not None:
1439
self._real_repository.unlock()
1441
# The rpc-level lock should be released even if there was a
1442
# problem releasing the vfs-based lock.
1444
# Only write-locked repositories need to make a remote method
1445
# call to perform the unlock.
1446
old_token = self._lock_token
1447
self._lock_token = None
1448
if not self._leave_lock:
1449
self._unlock(old_token)
1450
# Fallbacks are always 'lock_read()' so we don't pay attention to
1452
for repo in self._fallback_repositories:
1455
def break_lock(self):
1456
# should hand off to the network
1458
return self._real_repository.break_lock()
1460
def _get_tarball(self, compression):
1461
"""Return a TemporaryFile containing a repository tarball.
1463
Returns None if the server does not support sending tarballs.
1466
path = self.bzrdir._path_for_remote_call(self._client)
1468
response, protocol = self._call_expecting_body(
1469
'Repository.tarball', path, compression)
1470
except errors.UnknownSmartMethod:
1471
protocol.cancel_read_body()
1473
if response[0] == 'ok':
1474
# Extract the tarball and return it
1475
t = tempfile.NamedTemporaryFile()
1476
# TODO: rpc layer should read directly into it...
1477
t.write(protocol.read_body_bytes())
1480
raise errors.UnexpectedSmartServerResponse(response)
1482
def sprout(self, to_bzrdir, revision_id=None):
1483
# TODO: Option to control what format is created?
1485
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1487
dest_repo.fetch(self, revision_id=revision_id)
1490
### These methods are just thin shims to the VFS object for now.
1492
def revision_tree(self, revision_id):
1494
return self._real_repository.revision_tree(revision_id)
1496
def get_serializer_format(self):
1498
return self._real_repository.get_serializer_format()
1500
def get_commit_builder(self, branch, parents, config, timestamp=None,
1501
timezone=None, committer=None, revprops=None,
1502
revision_id=None, lossy=False):
1503
# FIXME: It ought to be possible to call this without immediately
1504
# triggering _ensure_real. For now it's the easiest thing to do.
1506
real_repo = self._real_repository
1507
builder = real_repo.get_commit_builder(branch, parents,
1508
config, timestamp=timestamp, timezone=timezone,
1509
committer=committer, revprops=revprops,
1510
revision_id=revision_id, lossy=lossy)
1513
def add_fallback_repository(self, repository):
1514
"""Add a repository to use for looking up data not held locally.
1516
:param repository: A repository.
1518
if not self._format.supports_external_lookups:
1519
raise errors.UnstackableRepositoryFormat(
1520
self._format.network_name(), self.base)
1521
# We need to accumulate additional repositories here, to pass them in
1524
# Make the check before we lock: this raises an exception.
1525
self._check_fallback_repository(repository)
1526
if self.is_locked():
1527
# We will call fallback.unlock() when we transition to the unlocked
1528
# state, so always add a lock here. If a caller passes us a locked
1529
# repository, they are responsible for unlocking it later.
1530
repository.lock_read()
1531
self._fallback_repositories.append(repository)
1532
# If self._real_repository was parameterised already (e.g. because a
1533
# _real_branch had its get_stacked_on_url method called), then the
1534
# repository to be added may already be in the _real_repositories list.
1535
if self._real_repository is not None:
1536
fallback_locations = [repo.user_url for repo in
1537
self._real_repository._fallback_repositories]
1538
if repository.user_url not in fallback_locations:
1539
self._real_repository.add_fallback_repository(repository)
1541
def _check_fallback_repository(self, repository):
1542
"""Check that this repository can fallback to repository safely.
1544
Raise an error if not.
1546
:param repository: A repository to fallback to.
1548
return _mod_repository.InterRepository._assert_same_model(
1551
def add_inventory(self, revid, inv, parents):
1553
return self._real_repository.add_inventory(revid, inv, parents)
1555
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1556
parents, basis_inv=None, propagate_caches=False):
1558
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1559
delta, new_revision_id, parents, basis_inv=basis_inv,
1560
propagate_caches=propagate_caches)
1562
def add_revision(self, rev_id, rev, inv=None, config=None):
1564
return self._real_repository.add_revision(
1565
rev_id, rev, inv=inv, config=config)
1568
def get_inventory(self, revision_id):
1570
return self._real_repository.get_inventory(revision_id)
1572
def iter_inventories(self, revision_ids, ordering=None):
1574
return self._real_repository.iter_inventories(revision_ids, ordering)
1577
def get_revision(self, revision_id):
1579
return self._real_repository.get_revision(revision_id)
1581
def get_transaction(self):
1583
return self._real_repository.get_transaction()
1586
def clone(self, a_bzrdir, revision_id=None):
1588
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1590
def make_working_trees(self):
1591
"""See Repository.make_working_trees"""
1593
return self._real_repository.make_working_trees()
1595
def refresh_data(self):
1596
"""Re-read any data needed to synchronise with disk.
1598
This method is intended to be called after another repository instance
1599
(such as one used by a smart server) has inserted data into the
1600
repository. On all repositories this will work outside of write groups.
1601
Some repository formats (pack and newer for bzrlib native formats)
1602
support refresh_data inside write groups. If called inside a write
1603
group on a repository that does not support refreshing in a write group
1604
IsInWriteGroupError will be raised.
1606
if self._real_repository is not None:
1607
self._real_repository.refresh_data()
1609
def revision_ids_to_search_result(self, result_set):
1610
"""Convert a set of revision ids to a graph SearchResult."""
1611
result_parents = set()
1612
for parents in self.get_graph().get_parent_map(
1613
result_set).itervalues():
1614
result_parents.update(parents)
1615
included_keys = result_set.intersection(result_parents)
1616
start_keys = result_set.difference(included_keys)
1617
exclude_keys = result_parents.difference(result_set)
1618
result = graph.SearchResult(start_keys, exclude_keys,
1619
len(result_set), result_set)
1623
def search_missing_revision_ids(self, other,
1624
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1625
find_ghosts=True, revision_ids=None, if_present_ids=None,
1627
"""Return the revision ids that other has that this does not.
1629
These are returned in topological order.
1631
revision_id: only return revision ids included by revision_id.
1633
if symbol_versioning.deprecated_passed(revision_id):
1634
symbol_versioning.warn(
1635
'search_missing_revision_ids(revision_id=...) was '
1636
'deprecated in 2.4. Use revision_ids=[...] instead.',
1637
DeprecationWarning, stacklevel=2)
1638
if revision_ids is not None:
1639
raise AssertionError(
1640
'revision_ids is mutually exclusive with revision_id')
1641
if revision_id is not None:
1642
revision_ids = [revision_id]
1643
inter_repo = _mod_repository.InterRepository.get(other, self)
1644
return inter_repo.search_missing_revision_ids(
1645
find_ghosts=find_ghosts, revision_ids=revision_ids,
1646
if_present_ids=if_present_ids, limit=limit)
1648
def fetch(self, source, revision_id=None, find_ghosts=False,
1650
# No base implementation to use as RemoteRepository is not a subclass
1651
# of Repository; so this is a copy of Repository.fetch().
1652
if fetch_spec is not None and revision_id is not None:
1653
raise AssertionError(
1654
"fetch_spec and revision_id are mutually exclusive.")
1655
if self.is_in_write_group():
1656
raise errors.InternalBzrError(
1657
"May not fetch while in a write group.")
1658
# fast path same-url fetch operations
1659
if (self.has_same_location(source)
1660
and fetch_spec is None
1661
and self._has_same_fallbacks(source)):
1662
# check that last_revision is in 'from' and then return a
1664
if (revision_id is not None and
1665
not _mod_revision.is_null(revision_id)):
1666
self.get_revision(revision_id)
1668
# if there is no specific appropriate InterRepository, this will get
1669
# the InterRepository base class, which raises an
1670
# IncompatibleRepositories when asked to fetch.
1671
inter = _mod_repository.InterRepository.get(source, self)
1672
return inter.fetch(revision_id=revision_id,
1673
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1675
def create_bundle(self, target, base, fileobj, format=None):
1677
self._real_repository.create_bundle(target, base, fileobj, format)
1680
@symbol_versioning.deprecated_method(
1681
symbol_versioning.deprecated_in((2, 4, 0)))
1682
def get_ancestry(self, revision_id, topo_sorted=True):
1684
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1686
def fileids_altered_by_revision_ids(self, revision_ids):
1688
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1690
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1692
return self._real_repository._get_versioned_file_checker(
1693
revisions, revision_versions_cache)
1695
def iter_files_bytes(self, desired_files):
1696
"""See Repository.iter_file_bytes.
1699
return self._real_repository.iter_files_bytes(desired_files)
1701
def get_cached_parent_map(self, revision_ids):
1702
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
1703
return self._unstacked_provider.get_cached_parent_map(revision_ids)
1705
def get_parent_map(self, revision_ids):
1706
"""See bzrlib.Graph.get_parent_map()."""
1707
return self._make_parents_provider().get_parent_map(revision_ids)
1709
def _get_parent_map_rpc(self, keys):
1710
"""Helper for get_parent_map that performs the RPC."""
1711
medium = self._client._medium
1712
if medium._is_remote_before((1, 2)):
1713
# We already found out that the server can't understand
1714
# Repository.get_parent_map requests, so just fetch the whole
1717
# Note that this reads the whole graph, when only some keys are
1718
# wanted. On this old server there's no way (?) to get them all
1719
# in one go, and the user probably will have seen a warning about
1720
# the server being old anyhow.
1721
rg = self._get_revision_graph(None)
1722
# There is an API discrepancy between get_parent_map and
1723
# get_revision_graph. Specifically, a "key:()" pair in
1724
# get_revision_graph just means a node has no parents. For
1725
# "get_parent_map" it means the node is a ghost. So fix up the
1726
# graph to correct this.
1727
# https://bugs.launchpad.net/bzr/+bug/214894
1728
# There is one other "bug" which is that ghosts in
1729
# get_revision_graph() are not returned at all. But we won't worry
1730
# about that for now.
1731
for node_id, parent_ids in rg.iteritems():
1732
if parent_ids == ():
1733
rg[node_id] = (NULL_REVISION,)
1734
rg[NULL_REVISION] = ()
1739
raise ValueError('get_parent_map(None) is not valid')
1740
if NULL_REVISION in keys:
1741
keys.discard(NULL_REVISION)
1742
found_parents = {NULL_REVISION:()}
1744
return found_parents
1747
# TODO(Needs analysis): We could assume that the keys being requested
1748
# from get_parent_map are in a breadth first search, so typically they
1749
# will all be depth N from some common parent, and we don't have to
1750
# have the server iterate from the root parent, but rather from the
1751
# keys we're searching; and just tell the server the keyspace we
1752
# already have; but this may be more traffic again.
1754
# Transform self._parents_map into a search request recipe.
1755
# TODO: Manage this incrementally to avoid covering the same path
1756
# repeatedly. (The server will have to on each request, but the less
1757
# work done the better).
1759
# Negative caching notes:
1760
# new server sends missing when a request including the revid
1761
# 'include-missing:' is present in the request.
1762
# missing keys are serialised as missing:X, and we then call
1763
# provider.note_missing(X) for-all X
1764
parents_map = self._unstacked_provider.get_cached_map()
1765
if parents_map is None:
1766
# Repository is not locked, so there's no cache.
1768
if _DEFAULT_SEARCH_DEPTH <= 0:
1769
(start_set, stop_keys,
1770
key_count) = graph.search_result_from_parent_map(
1771
parents_map, self._unstacked_provider.missing_keys)
1773
(start_set, stop_keys,
1774
key_count) = graph.limited_search_result_from_parent_map(
1775
parents_map, self._unstacked_provider.missing_keys,
1776
keys, depth=_DEFAULT_SEARCH_DEPTH)
1777
recipe = ('manual', start_set, stop_keys, key_count)
1778
body = self._serialise_search_recipe(recipe)
1779
path = self.bzrdir._path_for_remote_call(self._client)
1781
if type(key) is not str:
1783
"key %r not a plain string" % (key,))
1784
verb = 'Repository.get_parent_map'
1785
args = (path, 'include-missing:') + tuple(keys)
1787
response = self._call_with_body_bytes_expecting_body(
1789
except errors.UnknownSmartMethod:
1790
# Server does not support this method, so get the whole graph.
1791
# Worse, we have to force a disconnection, because the server now
1792
# doesn't realise it has a body on the wire to consume, so the
1793
# only way to recover is to abandon the connection.
1795
'Server is too old for fast get_parent_map, reconnecting. '
1796
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1798
# To avoid having to disconnect repeatedly, we keep track of the
1799
# fact the server doesn't understand remote methods added in 1.2.
1800
medium._remember_remote_is_before((1, 2))
1801
# Recurse just once and we should use the fallback code.
1802
return self._get_parent_map_rpc(keys)
1803
response_tuple, response_handler = response
1804
if response_tuple[0] not in ['ok']:
1805
response_handler.cancel_read_body()
1806
raise errors.UnexpectedSmartServerResponse(response_tuple)
1807
if response_tuple[0] == 'ok':
1808
coded = bz2.decompress(response_handler.read_body_bytes())
1810
# no revisions found
1812
lines = coded.split('\n')
1815
d = tuple(line.split())
1817
revision_graph[d[0]] = d[1:]
1820
if d[0].startswith('missing:'):
1822
self._unstacked_provider.note_missing_key(revid)
1824
# no parents - so give the Graph result
1826
revision_graph[d[0]] = (NULL_REVISION,)
1827
return revision_graph
1830
def get_signature_text(self, revision_id):
1832
return self._real_repository.get_signature_text(revision_id)
1835
def _get_inventory_xml(self, revision_id):
1837
return self._real_repository._get_inventory_xml(revision_id)
1839
def reconcile(self, other=None, thorough=False):
1841
return self._real_repository.reconcile(other=other, thorough=thorough)
1843
def all_revision_ids(self):
1845
return self._real_repository.all_revision_ids()
1848
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1850
return self._real_repository.get_deltas_for_revisions(revisions,
1851
specific_fileids=specific_fileids)
1854
def get_revision_delta(self, revision_id, specific_fileids=None):
1856
return self._real_repository.get_revision_delta(revision_id,
1857
specific_fileids=specific_fileids)
1860
def revision_trees(self, revision_ids):
1862
return self._real_repository.revision_trees(revision_ids)
1865
def get_revision_reconcile(self, revision_id):
1867
return self._real_repository.get_revision_reconcile(revision_id)
1870
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1872
return self._real_repository.check(revision_ids=revision_ids,
1873
callback_refs=callback_refs, check_repo=check_repo)
1875
def copy_content_into(self, destination, revision_id=None):
1877
return self._real_repository.copy_content_into(
1878
destination, revision_id=revision_id)
1880
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1881
# get a tarball of the remote repository, and copy from that into the
1883
from bzrlib import osutils
1885
# TODO: Maybe a progress bar while streaming the tarball?
1886
note(gettext("Copying repository content as tarball..."))
1887
tar_file = self._get_tarball('bz2')
1888
if tar_file is None:
1890
destination = to_bzrdir.create_repository()
1892
tar = tarfile.open('repository', fileobj=tar_file,
1894
tmpdir = osutils.mkdtemp()
1896
_extract_tar(tar, tmpdir)
1897
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1898
tmp_repo = tmp_bzrdir.open_repository()
1899
tmp_repo.copy_content_into(destination, revision_id)
1901
osutils.rmtree(tmpdir)
1905
# TODO: Suggestion from john: using external tar is much faster than
1906
# python's tarfile library, but it may not work on windows.
1909
def inventories(self):
1910
"""Decorate the real repository for now.
1912
In the long term a full blown network facility is needed to
1913
avoid creating a real repository object locally.
1916
return self._real_repository.inventories
1919
def pack(self, hint=None, clean_obsolete_packs=False):
1920
"""Compress the data within the repository.
1922
This is not currently implemented within the smart server.
1925
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1928
def revisions(self):
1929
"""Decorate the real repository for now.
1931
In the short term this should become a real object to intercept graph
1934
In the long term a full blown network facility is needed.
1937
return self._real_repository.revisions
1939
def set_make_working_trees(self, new_value):
1941
new_value_str = "True"
1943
new_value_str = "False"
1944
path = self.bzrdir._path_for_remote_call(self._client)
1946
response = self._call(
1947
'Repository.set_make_working_trees', path, new_value_str)
1948
except errors.UnknownSmartMethod:
1950
self._real_repository.set_make_working_trees(new_value)
1952
if response[0] != 'ok':
1953
raise errors.UnexpectedSmartServerResponse(response)
1956
def signatures(self):
1957
"""Decorate the real repository for now.
1959
In the long term a full blown network facility is needed to avoid
1960
creating a real repository object locally.
1963
return self._real_repository.signatures
1966
def sign_revision(self, revision_id, gpg_strategy):
1968
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1972
"""Decorate the real repository for now.
1974
In the long term a full blown network facility is needed to avoid
1975
creating a real repository object locally.
1978
return self._real_repository.texts
1981
def get_revisions(self, revision_ids):
1983
return self._real_repository.get_revisions(revision_ids)
1985
def supports_rich_root(self):
1986
return self._format.rich_root_data
1988
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1989
def iter_reverse_revision_history(self, revision_id):
1991
return self._real_repository.iter_reverse_revision_history(revision_id)
1994
def _serializer(self):
1995
return self._format._serializer
1997
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1999
return self._real_repository.store_revision_signature(
2000
gpg_strategy, plaintext, revision_id)
2002
def add_signature_text(self, revision_id, signature):
2004
return self._real_repository.add_signature_text(revision_id, signature)
2006
def has_signature_for_revision_id(self, revision_id):
2008
return self._real_repository.has_signature_for_revision_id(revision_id)
2010
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2012
return self._real_repository.item_keys_introduced_by(revision_ids,
2013
_files_pb=_files_pb)
2015
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2017
return self._real_repository._find_inconsistent_revision_parents(
2020
def _check_for_inconsistent_revision_parents(self):
2022
return self._real_repository._check_for_inconsistent_revision_parents()
2024
def _make_parents_provider(self, other=None):
2025
providers = [self._unstacked_provider]
2026
if other is not None:
2027
providers.insert(0, other)
2028
return graph.StackedParentsProvider(_LazyListJoin(
2029
providers, self._fallback_repositories))
2031
def _serialise_search_recipe(self, recipe):
2032
"""Serialise a graph search recipe.
2034
:param recipe: A search recipe (start, stop, count).
2035
:return: Serialised bytes.
2037
start_keys = ' '.join(recipe[1])
2038
stop_keys = ' '.join(recipe[2])
2039
count = str(recipe[3])
2040
return '\n'.join((start_keys, stop_keys, count))
2042
def _serialise_search_result(self, search_result):
2043
parts = search_result.get_network_struct()
2044
return '\n'.join(parts)
2047
path = self.bzrdir._path_for_remote_call(self._client)
2049
response = self._call('PackRepository.autopack', path)
2050
except errors.UnknownSmartMethod:
2052
self._real_repository._pack_collection.autopack()
2055
if response[0] != 'ok':
2056
raise errors.UnexpectedSmartServerResponse(response)
2059
class RemoteStreamSink(vf_repository.StreamSink):
2061
def _insert_real(self, stream, src_format, resume_tokens):
2062
self.target_repo._ensure_real()
2063
sink = self.target_repo._real_repository._get_sink()
2064
result = sink.insert_stream(stream, src_format, resume_tokens)
2066
self.target_repo.autopack()
2069
def insert_stream(self, stream, src_format, resume_tokens):
2070
target = self.target_repo
2071
target._unstacked_provider.missing_keys.clear()
2072
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2073
if target._lock_token:
2074
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2075
lock_args = (target._lock_token or '',)
2077
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2079
client = target._client
2080
medium = client._medium
2081
path = target.bzrdir._path_for_remote_call(client)
2082
# Probe for the verb to use with an empty stream before sending the
2083
# real stream to it. We do this both to avoid the risk of sending a
2084
# large request that is then rejected, and because we don't want to
2085
# implement a way to buffer, rewind, or restart the stream.
2087
for verb, required_version in candidate_calls:
2088
if medium._is_remote_before(required_version):
2091
# We've already done the probing (and set _is_remote_before) on
2092
# a previous insert.
2095
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2097
response = client.call_with_body_stream(
2098
(verb, path, '') + lock_args, byte_stream)
2099
except errors.UnknownSmartMethod:
2100
medium._remember_remote_is_before(required_version)
2106
return self._insert_real(stream, src_format, resume_tokens)
2107
self._last_inv_record = None
2108
self._last_substream = None
2109
if required_version < (1, 19):
2110
# Remote side doesn't support inventory deltas. Wrap the stream to
2111
# make sure we don't send any. If the stream contains inventory
2112
# deltas we'll interrupt the smart insert_stream request and
2114
stream = self._stop_stream_if_inventory_delta(stream)
2115
byte_stream = smart_repo._stream_to_byte_stream(
2117
resume_tokens = ' '.join(resume_tokens)
2118
response = client.call_with_body_stream(
2119
(verb, path, resume_tokens) + lock_args, byte_stream)
2120
if response[0][0] not in ('ok', 'missing-basis'):
2121
raise errors.UnexpectedSmartServerResponse(response)
2122
if self._last_substream is not None:
2123
# The stream included an inventory-delta record, but the remote
2124
# side isn't new enough to support them. So we need to send the
2125
# rest of the stream via VFS.
2126
self.target_repo.refresh_data()
2127
return self._resume_stream_with_vfs(response, src_format)
2128
if response[0][0] == 'missing-basis':
2129
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2130
resume_tokens = tokens
2131
return resume_tokens, set(missing_keys)
2133
self.target_repo.refresh_data()
2136
def _resume_stream_with_vfs(self, response, src_format):
2137
"""Resume sending a stream via VFS, first resending the record and
2138
substream that couldn't be sent via an insert_stream verb.
2140
if response[0][0] == 'missing-basis':
2141
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2142
# Ignore missing_keys, we haven't finished inserting yet
2145
def resume_substream():
2146
# Yield the substream that was interrupted.
2147
for record in self._last_substream:
2149
self._last_substream = None
2150
def resume_stream():
2151
# Finish sending the interrupted substream
2152
yield ('inventory-deltas', resume_substream())
2153
# Then simply continue sending the rest of the stream.
2154
for substream_kind, substream in self._last_stream:
2155
yield substream_kind, substream
2156
return self._insert_real(resume_stream(), src_format, tokens)
2158
def _stop_stream_if_inventory_delta(self, stream):
2159
"""Normally this just lets the original stream pass-through unchanged.
2161
However if any 'inventory-deltas' substream occurs it will stop
2162
streaming, and store the interrupted substream and stream in
2163
self._last_substream and self._last_stream so that the stream can be
2164
resumed by _resume_stream_with_vfs.
2167
stream_iter = iter(stream)
2168
for substream_kind, substream in stream_iter:
2169
if substream_kind == 'inventory-deltas':
2170
self._last_substream = substream
2171
self._last_stream = stream_iter
2174
yield substream_kind, substream
2177
class RemoteStreamSource(vf_repository.StreamSource):
2178
"""Stream data from a remote server."""
2180
def get_stream(self, search):
2181
if (self.from_repository._fallback_repositories and
2182
self.to_format._fetch_order == 'topological'):
2183
return self._real_stream(self.from_repository, search)
2186
repos = [self.from_repository]
2192
repos.extend(repo._fallback_repositories)
2193
sources.append(repo)
2194
return self.missing_parents_chain(search, sources)
2196
def get_stream_for_missing_keys(self, missing_keys):
2197
self.from_repository._ensure_real()
2198
real_repo = self.from_repository._real_repository
2199
real_source = real_repo._get_source(self.to_format)
2200
return real_source.get_stream_for_missing_keys(missing_keys)
2202
def _real_stream(self, repo, search):
2203
"""Get a stream for search from repo.
2205
This never called RemoteStreamSource.get_stream, and is a heler
2206
for RemoteStreamSource._get_stream to allow getting a stream
2207
reliably whether fallback back because of old servers or trying
2208
to stream from a non-RemoteRepository (which the stacked support
2211
source = repo._get_source(self.to_format)
2212
if isinstance(source, RemoteStreamSource):
2214
source = repo._real_repository._get_source(self.to_format)
2215
return source.get_stream(search)
2217
def _get_stream(self, repo, search):
2218
"""Core worker to get a stream from repo for search.
2220
This is used by both get_stream and the stacking support logic. It
2221
deliberately gets a stream for repo which does not need to be
2222
self.from_repository. In the event that repo is not Remote, or
2223
cannot do a smart stream, a fallback is made to the generic
2224
repository._get_stream() interface, via self._real_stream.
2226
In the event of stacking, streams from _get_stream will not
2227
contain all the data for search - this is normal (see get_stream).
2229
:param repo: A repository.
2230
:param search: A search.
2232
# Fallbacks may be non-smart
2233
if not isinstance(repo, RemoteRepository):
2234
return self._real_stream(repo, search)
2235
client = repo._client
2236
medium = client._medium
2237
path = repo.bzrdir._path_for_remote_call(client)
2238
search_bytes = repo._serialise_search_result(search)
2239
args = (path, self.to_format.network_name())
2241
('Repository.get_stream_1.19', (1, 19)),
2242
('Repository.get_stream', (1, 13))]
2245
for verb, version in candidate_verbs:
2246
if medium._is_remote_before(version):
2249
response = repo._call_with_body_bytes_expecting_body(
2250
verb, args, search_bytes)
2251
except errors.UnknownSmartMethod:
2252
medium._remember_remote_is_before(version)
2253
except errors.UnknownErrorFromSmartServer, e:
2254
if isinstance(search, graph.EverythingResult):
2255
error_verb = e.error_from_smart_server.error_verb
2256
if error_verb == 'BadSearch':
2257
# Pre-2.4 servers don't support this sort of search.
2258
# XXX: perhaps falling back to VFS on BadSearch is a
2259
# good idea in general? It might provide a little bit
2260
# of protection against client-side bugs.
2261
medium._remember_remote_is_before((2, 4))
2265
response_tuple, response_handler = response
2269
return self._real_stream(repo, search)
2270
if response_tuple[0] != 'ok':
2271
raise errors.UnexpectedSmartServerResponse(response_tuple)
2272
byte_stream = response_handler.read_streamed_body()
2273
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2274
self._record_counter)
2275
if src_format.network_name() != repo._format.network_name():
2276
raise AssertionError(
2277
"Mismatched RemoteRepository and stream src %r, %r" % (
2278
src_format.network_name(), repo._format.network_name()))
2281
def missing_parents_chain(self, search, sources):
2282
"""Chain multiple streams together to handle stacking.
2284
:param search: The overall search to satisfy with streams.
2285
:param sources: A list of Repository objects to query.
2287
self.from_serialiser = self.from_repository._format._serializer
2288
self.seen_revs = set()
2289
self.referenced_revs = set()
2290
# If there are heads in the search, or the key count is > 0, we are not
2292
while not search.is_empty() and len(sources) > 1:
2293
source = sources.pop(0)
2294
stream = self._get_stream(source, search)
2295
for kind, substream in stream:
2296
if kind != 'revisions':
2297
yield kind, substream
2299
yield kind, self.missing_parents_rev_handler(substream)
2300
search = search.refine(self.seen_revs, self.referenced_revs)
2301
self.seen_revs = set()
2302
self.referenced_revs = set()
2303
if not search.is_empty():
2304
for kind, stream in self._get_stream(sources[0], search):
2307
def missing_parents_rev_handler(self, substream):
2308
for content in substream:
2309
revision_bytes = content.get_bytes_as('fulltext')
2310
revision = self.from_serialiser.read_revision_from_string(
2312
self.seen_revs.add(content.key[-1])
2313
self.referenced_revs.update(revision.parent_ids)
2317
class RemoteBranchLockableFiles(LockableFiles):
2318
"""A 'LockableFiles' implementation that talks to a smart server.
2320
This is not a public interface class.
2323
def __init__(self, bzrdir, _client):
2324
self.bzrdir = bzrdir
2325
self._client = _client
2326
self._need_find_modes = True
2327
LockableFiles.__init__(
2328
self, bzrdir.get_branch_transport(None),
2329
'lock', lockdir.LockDir)
2331
def _find_modes(self):
2332
# RemoteBranches don't let the client set the mode of control files.
2333
self._dir_mode = None
2334
self._file_mode = None
2337
class RemoteBranchFormat(branch.BranchFormat):
2339
def __init__(self, network_name=None):
2340
super(RemoteBranchFormat, self).__init__()
2341
self._matchingbzrdir = RemoteBzrDirFormat()
2342
self._matchingbzrdir.set_branch_format(self)
2343
self._custom_format = None
2344
self._network_name = network_name
2346
def __eq__(self, other):
2347
return (isinstance(other, RemoteBranchFormat) and
2348
self.__dict__ == other.__dict__)
2350
def _ensure_real(self):
2351
if self._custom_format is None:
2352
self._custom_format = branch.network_format_registry.get(
2355
def get_format_description(self):
2357
return 'Remote: ' + self._custom_format.get_format_description()
2359
def network_name(self):
2360
return self._network_name
2362
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2363
return a_bzrdir.open_branch(name=name,
2364
ignore_fallbacks=ignore_fallbacks)
2366
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
2367
# Initialisation when using a local bzrdir object, or a non-vfs init
2368
# method is not available on the server.
2369
# self._custom_format is always set - the start of initialize ensures
2371
if isinstance(a_bzrdir, RemoteBzrDir):
2372
a_bzrdir._ensure_real()
2373
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2374
name, append_revisions_only=append_revisions_only)
2376
# We assume the bzrdir is parameterised; it may not be.
2377
result = self._custom_format.initialize(a_bzrdir, name,
2378
append_revisions_only=append_revisions_only)
2379
if (isinstance(a_bzrdir, RemoteBzrDir) and
2380
not isinstance(result, RemoteBranch)):
2381
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2385
def initialize(self, a_bzrdir, name=None, repository=None,
2386
append_revisions_only=None):
2387
# 1) get the network name to use.
2388
if self._custom_format:
2389
network_name = self._custom_format.network_name()
2391
# Select the current bzrlib default and ask for that.
2392
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2393
reference_format = reference_bzrdir_format.get_branch_format()
2394
self._custom_format = reference_format
2395
network_name = reference_format.network_name()
2396
# Being asked to create on a non RemoteBzrDir:
2397
if not isinstance(a_bzrdir, RemoteBzrDir):
2398
return self._vfs_initialize(a_bzrdir, name=name,
2399
append_revisions_only=append_revisions_only)
2400
medium = a_bzrdir._client._medium
2401
if medium._is_remote_before((1, 13)):
2402
return self._vfs_initialize(a_bzrdir, name=name,
2403
append_revisions_only=append_revisions_only)
2404
# Creating on a remote bzr dir.
2405
# 2) try direct creation via RPC
2406
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2407
if name is not None:
2408
# XXX JRV20100304: Support creating colocated branches
2409
raise errors.NoColocatedBranchSupport(self)
2410
verb = 'BzrDir.create_branch'
2412
response = a_bzrdir._call(verb, path, network_name)
2413
except errors.UnknownSmartMethod:
2414
# Fallback - use vfs methods
2415
medium._remember_remote_is_before((1, 13))
2416
return self._vfs_initialize(a_bzrdir, name=name,
2417
append_revisions_only=append_revisions_only)
2418
if response[0] != 'ok':
2419
raise errors.UnexpectedSmartServerResponse(response)
2420
# Turn the response into a RemoteRepository object.
2421
format = RemoteBranchFormat(network_name=response[1])
2422
repo_format = response_tuple_to_repo_format(response[3:])
2423
repo_path = response[2]
2424
if repository is not None:
2425
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2426
url_diff = urlutils.relative_url(repository.user_url,
2429
raise AssertionError(
2430
'repository.user_url %r does not match URL from server '
2431
'response (%r + %r)'
2432
% (repository.user_url, a_bzrdir.user_url, repo_path))
2433
remote_repo = repository
2436
repo_bzrdir = a_bzrdir
2438
repo_bzrdir = RemoteBzrDir(
2439
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2441
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2442
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2443
format=format, setup_stacking=False, name=name)
2444
if append_revisions_only:
2445
remote_branch.set_append_revisions_only(append_revisions_only)
2446
# XXX: We know this is a new branch, so it must have revno 0, revid
2447
# NULL_REVISION. Creating the branch locked would make this be unable
2448
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2449
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2450
return remote_branch
2452
def make_tags(self, branch):
2454
return self._custom_format.make_tags(branch)
2456
def supports_tags(self):
2457
# Remote branches might support tags, but we won't know until we
2458
# access the real remote branch.
2460
return self._custom_format.supports_tags()
2462
def supports_stacking(self):
2464
return self._custom_format.supports_stacking()
2466
def supports_set_append_revisions_only(self):
2468
return self._custom_format.supports_set_append_revisions_only()
2470
def _use_default_local_heads_to_fetch(self):
2471
# If the branch format is a metadir format *and* its heads_to_fetch
2472
# implementation is not overridden vs the base class, we can use the
2473
# base class logic rather than use the heads_to_fetch RPC. This is
2474
# usually cheaper in terms of net round trips, as the last-revision and
2475
# tags info fetched is cached and would be fetched anyway.
2477
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2478
branch_class = self._custom_format._branch_class()
2479
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2480
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2484
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2485
"""Branch stored on a server accessed by HPSS RPC.
2487
At the moment most operations are mapped down to simple file operations.
2490
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2491
_client=None, format=None, setup_stacking=True, name=None):
2492
"""Create a RemoteBranch instance.
2494
:param real_branch: An optional local implementation of the branch
2495
format, usually accessing the data via the VFS.
2496
:param _client: Private parameter for testing.
2497
:param format: A RemoteBranchFormat object, None to create one
2498
automatically. If supplied it should have a network_name already
2500
:param setup_stacking: If True make an RPC call to determine the
2501
stacked (or not) status of the branch. If False assume the branch
2503
:param name: Colocated branch name
2505
# We intentionally don't call the parent class's __init__, because it
2506
# will try to assign to self.tags, which is a property in this subclass.
2507
# And the parent's __init__ doesn't do much anyway.
2508
self.bzrdir = remote_bzrdir
2509
if _client is not None:
2510
self._client = _client
2512
self._client = remote_bzrdir._client
2513
self.repository = remote_repository
2514
if real_branch is not None:
2515
self._real_branch = real_branch
2516
# Give the remote repository the matching real repo.
2517
real_repo = self._real_branch.repository
2518
if isinstance(real_repo, RemoteRepository):
2519
real_repo._ensure_real()
2520
real_repo = real_repo._real_repository
2521
self.repository._set_real_repository(real_repo)
2522
# Give the branch the remote repository to let fast-pathing happen.
2523
self._real_branch.repository = self.repository
2525
self._real_branch = None
2526
# Fill out expected attributes of branch for bzrlib API users.
2527
self._clear_cached_state()
2528
# TODO: deprecate self.base in favor of user_url
2529
self.base = self.bzrdir.user_url
2531
self._control_files = None
2532
self._lock_mode = None
2533
self._lock_token = None
2534
self._repo_lock_token = None
2535
self._lock_count = 0
2536
self._leave_lock = False
2537
# Setup a format: note that we cannot call _ensure_real until all the
2538
# attributes above are set: This code cannot be moved higher up in this
2541
self._format = RemoteBranchFormat()
2542
if real_branch is not None:
2543
self._format._network_name = \
2544
self._real_branch._format.network_name()
2546
self._format = format
2547
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2548
# branch.open_branch method.
2549
self._real_ignore_fallbacks = not setup_stacking
2550
if not self._format._network_name:
2551
# Did not get from open_branchV2 - old server.
2553
self._format._network_name = \
2554
self._real_branch._format.network_name()
2555
self.tags = self._format.make_tags(self)
2556
# The base class init is not called, so we duplicate this:
2557
hooks = branch.Branch.hooks['open']
2560
self._is_stacked = False
2562
self._setup_stacking()
2564
def _setup_stacking(self):
2565
# configure stacking into the remote repository, by reading it from
2568
fallback_url = self.get_stacked_on_url()
2569
except (errors.NotStacked, errors.UnstackableBranchFormat,
2570
errors.UnstackableRepositoryFormat), e:
2572
self._is_stacked = True
2573
self._activate_fallback_location(fallback_url)
2575
def _get_config(self):
2576
return RemoteBranchConfig(self)
2578
def _get_real_transport(self):
2579
# if we try vfs access, return the real branch's vfs transport
2581
return self._real_branch._transport
2583
_transport = property(_get_real_transport)
2586
return "%s(%s)" % (self.__class__.__name__, self.base)
2590
def _ensure_real(self):
2591
"""Ensure that there is a _real_branch set.
2593
Used before calls to self._real_branch.
2595
if self._real_branch is None:
2596
if not vfs.vfs_enabled():
2597
raise AssertionError('smart server vfs must be enabled '
2598
'to use vfs implementation')
2599
self.bzrdir._ensure_real()
2600
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2601
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2602
if self.repository._real_repository is None:
2603
# Give the remote repository the matching real repo.
2604
real_repo = self._real_branch.repository
2605
if isinstance(real_repo, RemoteRepository):
2606
real_repo._ensure_real()
2607
real_repo = real_repo._real_repository
2608
self.repository._set_real_repository(real_repo)
2609
# Give the real branch the remote repository to let fast-pathing
2611
self._real_branch.repository = self.repository
2612
if self._lock_mode == 'r':
2613
self._real_branch.lock_read()
2614
elif self._lock_mode == 'w':
2615
self._real_branch.lock_write(token=self._lock_token)
2617
def _translate_error(self, err, **context):
2618
self.repository._translate_error(err, branch=self, **context)
2620
def _clear_cached_state(self):
2621
super(RemoteBranch, self)._clear_cached_state()
2622
if self._real_branch is not None:
2623
self._real_branch._clear_cached_state()
2625
def _clear_cached_state_of_remote_branch_only(self):
2626
"""Like _clear_cached_state, but doesn't clear the cache of
2629
This is useful when falling back to calling a method of
2630
self._real_branch that changes state. In that case the underlying
2631
branch changes, so we need to invalidate this RemoteBranch's cache of
2632
it. However, there's no need to invalidate the _real_branch's cache
2633
too, in fact doing so might harm performance.
2635
super(RemoteBranch, self)._clear_cached_state()
2638
def control_files(self):
2639
# Defer actually creating RemoteBranchLockableFiles until its needed,
2640
# because it triggers an _ensure_real that we otherwise might not need.
2641
if self._control_files is None:
2642
self._control_files = RemoteBranchLockableFiles(
2643
self.bzrdir, self._client)
2644
return self._control_files
2646
def _get_checkout_format(self, lightweight=False):
2649
format = RemoteBzrDirFormat()
2650
self.bzrdir._format._supply_sub_formats_to(format)
2651
format.workingtree_format = self._real_branch._get_checkout_format(
2652
lightweight=lightweight).workingtree_format
2655
return self._real_branch._get_checkout_format(lightweight=False)
2657
def get_physical_lock_status(self):
2658
"""See Branch.get_physical_lock_status()."""
2659
# should be an API call to the server, as branches must be lockable.
2661
return self._real_branch.get_physical_lock_status()
2663
def get_stacked_on_url(self):
2664
"""Get the URL this branch is stacked against.
2666
:raises NotStacked: If the branch is not stacked.
2667
:raises UnstackableBranchFormat: If the branch does not support
2669
:raises UnstackableRepositoryFormat: If the repository does not support
2673
# there may not be a repository yet, so we can't use
2674
# self._translate_error, so we can't use self._call either.
2675
response = self._client.call('Branch.get_stacked_on_url',
2676
self._remote_path())
2677
except errors.ErrorFromSmartServer, err:
2678
# there may not be a repository yet, so we can't call through
2679
# its _translate_error
2680
_translate_error(err, branch=self)
2681
except errors.UnknownSmartMethod, err:
2683
return self._real_branch.get_stacked_on_url()
2684
if response[0] != 'ok':
2685
raise errors.UnexpectedSmartServerResponse(response)
2688
def set_stacked_on_url(self, url):
2689
branch.Branch.set_stacked_on_url(self, url)
2691
self._is_stacked = False
2693
self._is_stacked = True
2695
def _vfs_get_tags_bytes(self):
2697
return self._real_branch._get_tags_bytes()
2700
def _get_tags_bytes(self):
2701
if self._tags_bytes is None:
2702
self._tags_bytes = self._get_tags_bytes_via_hpss()
2703
return self._tags_bytes
2705
def _get_tags_bytes_via_hpss(self):
2706
medium = self._client._medium
2707
if medium._is_remote_before((1, 13)):
2708
return self._vfs_get_tags_bytes()
2710
response = self._call('Branch.get_tags_bytes', self._remote_path())
2711
except errors.UnknownSmartMethod:
2712
medium._remember_remote_is_before((1, 13))
2713
return self._vfs_get_tags_bytes()
2716
def _vfs_set_tags_bytes(self, bytes):
2718
return self._real_branch._set_tags_bytes(bytes)
2720
def _set_tags_bytes(self, bytes):
2721
if self.is_locked():
2722
self._tags_bytes = bytes
2723
medium = self._client._medium
2724
if medium._is_remote_before((1, 18)):
2725
self._vfs_set_tags_bytes(bytes)
2729
self._remote_path(), self._lock_token, self._repo_lock_token)
2730
response = self._call_with_body_bytes(
2731
'Branch.set_tags_bytes', args, bytes)
2732
except errors.UnknownSmartMethod:
2733
medium._remember_remote_is_before((1, 18))
2734
self._vfs_set_tags_bytes(bytes)
2736
def lock_read(self):
2737
"""Lock the branch for read operations.
2739
:return: A bzrlib.lock.LogicalLockResult.
2741
self.repository.lock_read()
2742
if not self._lock_mode:
2743
self._note_lock('r')
2744
self._lock_mode = 'r'
2745
self._lock_count = 1
2746
if self._real_branch is not None:
2747
self._real_branch.lock_read()
2749
self._lock_count += 1
2750
return lock.LogicalLockResult(self.unlock)
2752
def _remote_lock_write(self, token):
2754
branch_token = repo_token = ''
2756
branch_token = token
2757
repo_token = self.repository.lock_write().repository_token
2758
self.repository.unlock()
2759
err_context = {'token': token}
2761
response = self._call(
2762
'Branch.lock_write', self._remote_path(), branch_token,
2763
repo_token or '', **err_context)
2764
except errors.LockContention, e:
2765
# The LockContention from the server doesn't have any
2766
# information about the lock_url. We re-raise LockContention
2767
# with valid lock_url.
2768
raise errors.LockContention('(remote lock)',
2769
self.repository.base.split('.bzr/')[0])
2770
if response[0] != 'ok':
2771
raise errors.UnexpectedSmartServerResponse(response)
2772
ok, branch_token, repo_token = response
2773
return branch_token, repo_token
2775
def lock_write(self, token=None):
2776
if not self._lock_mode:
2777
self._note_lock('w')
2778
# Lock the branch and repo in one remote call.
2779
remote_tokens = self._remote_lock_write(token)
2780
self._lock_token, self._repo_lock_token = remote_tokens
2781
if not self._lock_token:
2782
raise SmartProtocolError('Remote server did not return a token!')
2783
# Tell the self.repository object that it is locked.
2784
self.repository.lock_write(
2785
self._repo_lock_token, _skip_rpc=True)
2787
if self._real_branch is not None:
2788
self._real_branch.lock_write(token=self._lock_token)
2789
if token is not None:
2790
self._leave_lock = True
2792
self._leave_lock = False
2793
self._lock_mode = 'w'
2794
self._lock_count = 1
2795
elif self._lock_mode == 'r':
2796
raise errors.ReadOnlyError(self)
2798
if token is not None:
2799
# A token was given to lock_write, and we're relocking, so
2800
# check that the given token actually matches the one we
2802
if token != self._lock_token:
2803
raise errors.TokenMismatch(token, self._lock_token)
2804
self._lock_count += 1
2805
# Re-lock the repository too.
2806
self.repository.lock_write(self._repo_lock_token)
2807
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2809
def _unlock(self, branch_token, repo_token):
2810
err_context = {'token': str((branch_token, repo_token))}
2811
response = self._call(
2812
'Branch.unlock', self._remote_path(), branch_token,
2813
repo_token or '', **err_context)
2814
if response == ('ok',):
2816
raise errors.UnexpectedSmartServerResponse(response)
2818
@only_raises(errors.LockNotHeld, errors.LockBroken)
2821
self._lock_count -= 1
2822
if not self._lock_count:
2823
self._clear_cached_state()
2824
mode = self._lock_mode
2825
self._lock_mode = None
2826
if self._real_branch is not None:
2827
if (not self._leave_lock and mode == 'w' and
2828
self._repo_lock_token):
2829
# If this RemoteBranch will remove the physical lock
2830
# for the repository, make sure the _real_branch
2831
# doesn't do it first. (Because the _real_branch's
2832
# repository is set to be the RemoteRepository.)
2833
self._real_branch.repository.leave_lock_in_place()
2834
self._real_branch.unlock()
2836
# Only write-locked branched need to make a remote method
2837
# call to perform the unlock.
2839
if not self._lock_token:
2840
raise AssertionError('Locked, but no token!')
2841
branch_token = self._lock_token
2842
repo_token = self._repo_lock_token
2843
self._lock_token = None
2844
self._repo_lock_token = None
2845
if not self._leave_lock:
2846
self._unlock(branch_token, repo_token)
2848
self.repository.unlock()
2850
def break_lock(self):
2852
return self._real_branch.break_lock()
2854
def leave_lock_in_place(self):
2855
if not self._lock_token:
2856
raise NotImplementedError(self.leave_lock_in_place)
2857
self._leave_lock = True
2859
def dont_leave_lock_in_place(self):
2860
if not self._lock_token:
2861
raise NotImplementedError(self.dont_leave_lock_in_place)
2862
self._leave_lock = False
2865
def get_rev_id(self, revno, history=None):
2867
return _mod_revision.NULL_REVISION
2868
last_revision_info = self.last_revision_info()
2869
ok, result = self.repository.get_rev_id_for_revno(
2870
revno, last_revision_info)
2873
missing_parent = result[1]
2874
# Either the revision named by the server is missing, or its parent
2875
# is. Call get_parent_map to determine which, so that we report a
2877
parent_map = self.repository.get_parent_map([missing_parent])
2878
if missing_parent in parent_map:
2879
missing_parent = parent_map[missing_parent]
2880
raise errors.RevisionNotPresent(missing_parent, self.repository)
2882
def _read_last_revision_info(self):
2883
response = self._call('Branch.last_revision_info', self._remote_path())
2884
if response[0] != 'ok':
2885
raise SmartProtocolError('unexpected response code %s' % (response,))
2886
revno = int(response[1])
2887
last_revision = response[2]
2888
return (revno, last_revision)
2890
def _gen_revision_history(self):
2891
"""See Branch._gen_revision_history()."""
2892
if self._is_stacked:
2894
return self._real_branch._gen_revision_history()
2895
response_tuple, response_handler = self._call_expecting_body(
2896
'Branch.revision_history', self._remote_path())
2897
if response_tuple[0] != 'ok':
2898
raise errors.UnexpectedSmartServerResponse(response_tuple)
2899
result = response_handler.read_body_bytes().split('\x00')
2904
def _remote_path(self):
2905
return self.bzrdir._path_for_remote_call(self._client)
2907
def _set_last_revision_descendant(self, revision_id, other_branch,
2908
allow_diverged=False, allow_overwrite_descendant=False):
2909
# This performs additional work to meet the hook contract; while its
2910
# undesirable, we have to synthesise the revno to call the hook, and
2911
# not calling the hook is worse as it means changes can't be prevented.
2912
# Having calculated this though, we can't just call into
2913
# set_last_revision_info as a simple call, because there is a set_rh
2914
# hook that some folk may still be using.
2915
old_revno, old_revid = self.last_revision_info()
2916
history = self._lefthand_history(revision_id)
2917
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2918
err_context = {'other_branch': other_branch}
2919
response = self._call('Branch.set_last_revision_ex',
2920
self._remote_path(), self._lock_token, self._repo_lock_token,
2921
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2923
self._clear_cached_state()
2924
if len(response) != 3 and response[0] != 'ok':
2925
raise errors.UnexpectedSmartServerResponse(response)
2926
new_revno, new_revision_id = response[1:]
2927
self._last_revision_info_cache = new_revno, new_revision_id
2928
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2929
if self._real_branch is not None:
2930
cache = new_revno, new_revision_id
2931
self._real_branch._last_revision_info_cache = cache
2933
def _set_last_revision(self, revision_id):
2934
old_revno, old_revid = self.last_revision_info()
2935
# This performs additional work to meet the hook contract; while its
2936
# undesirable, we have to synthesise the revno to call the hook, and
2937
# not calling the hook is worse as it means changes can't be prevented.
2938
# Having calculated this though, we can't just call into
2939
# set_last_revision_info as a simple call, because there is a set_rh
2940
# hook that some folk may still be using.
2941
history = self._lefthand_history(revision_id)
2942
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2943
self._clear_cached_state()
2944
response = self._call('Branch.set_last_revision',
2945
self._remote_path(), self._lock_token, self._repo_lock_token,
2947
if response != ('ok',):
2948
raise errors.UnexpectedSmartServerResponse(response)
2949
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2951
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2953
def set_revision_history(self, rev_history):
2954
"""See Branch.set_revision_history."""
2955
self._set_revision_history(rev_history)
2958
def _set_revision_history(self, rev_history):
2959
# Send just the tip revision of the history; the server will generate
2960
# the full history from that. If the revision doesn't exist in this
2961
# branch, NoSuchRevision will be raised.
2962
if rev_history == []:
2965
rev_id = rev_history[-1]
2966
self._set_last_revision(rev_id)
2967
for hook in branch.Branch.hooks['set_rh']:
2968
hook(self, rev_history)
2969
self._cache_revision_history(rev_history)
2971
def _get_parent_location(self):
2972
medium = self._client._medium
2973
if medium._is_remote_before((1, 13)):
2974
return self._vfs_get_parent_location()
2976
response = self._call('Branch.get_parent', self._remote_path())
2977
except errors.UnknownSmartMethod:
2978
medium._remember_remote_is_before((1, 13))
2979
return self._vfs_get_parent_location()
2980
if len(response) != 1:
2981
raise errors.UnexpectedSmartServerResponse(response)
2982
parent_location = response[0]
2983
if parent_location == '':
2985
return parent_location
2987
def _vfs_get_parent_location(self):
2989
return self._real_branch._get_parent_location()
2991
def _set_parent_location(self, url):
2992
medium = self._client._medium
2993
if medium._is_remote_before((1, 15)):
2994
return self._vfs_set_parent_location(url)
2996
call_url = url or ''
2997
if type(call_url) is not str:
2998
raise AssertionError('url must be a str or None (%s)' % url)
2999
response = self._call('Branch.set_parent_location',
3000
self._remote_path(), self._lock_token, self._repo_lock_token,
3002
except errors.UnknownSmartMethod:
3003
medium._remember_remote_is_before((1, 15))
3004
return self._vfs_set_parent_location(url)
3006
raise errors.UnexpectedSmartServerResponse(response)
3008
def _vfs_set_parent_location(self, url):
3010
return self._real_branch._set_parent_location(url)
3013
def pull(self, source, overwrite=False, stop_revision=None,
3015
self._clear_cached_state_of_remote_branch_only()
3017
return self._real_branch.pull(
3018
source, overwrite=overwrite, stop_revision=stop_revision,
3019
_override_hook_target=self, **kwargs)
3022
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3024
return self._real_branch.push(
3025
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3026
_override_hook_source_branch=self)
3028
def is_locked(self):
3029
return self._lock_count >= 1
3032
def revision_id_to_revno(self, revision_id):
3034
return self._real_branch.revision_id_to_revno(revision_id)
3037
def set_last_revision_info(self, revno, revision_id):
3038
# XXX: These should be returned by the set_last_revision_info verb
3039
old_revno, old_revid = self.last_revision_info()
3040
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3041
if not revision_id or not isinstance(revision_id, basestring):
3042
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3044
response = self._call('Branch.set_last_revision_info',
3045
self._remote_path(), self._lock_token, self._repo_lock_token,
3046
str(revno), revision_id)
3047
except errors.UnknownSmartMethod:
3049
self._clear_cached_state_of_remote_branch_only()
3050
self._real_branch.set_last_revision_info(revno, revision_id)
3051
self._last_revision_info_cache = revno, revision_id
3053
if response == ('ok',):
3054
self._clear_cached_state()
3055
self._last_revision_info_cache = revno, revision_id
3056
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3057
# Update the _real_branch's cache too.
3058
if self._real_branch is not None:
3059
cache = self._last_revision_info_cache
3060
self._real_branch._last_revision_info_cache = cache
3062
raise errors.UnexpectedSmartServerResponse(response)
3065
def generate_revision_history(self, revision_id, last_rev=None,
3067
medium = self._client._medium
3068
if not medium._is_remote_before((1, 6)):
3069
# Use a smart method for 1.6 and above servers
3071
self._set_last_revision_descendant(revision_id, other_branch,
3072
allow_diverged=True, allow_overwrite_descendant=True)
3074
except errors.UnknownSmartMethod:
3075
medium._remember_remote_is_before((1, 6))
3076
self._clear_cached_state_of_remote_branch_only()
3077
self._set_revision_history(self._lefthand_history(revision_id,
3078
last_rev=last_rev,other_branch=other_branch))
3080
def set_push_location(self, location):
3082
return self._real_branch.set_push_location(location)
3084
def heads_to_fetch(self):
3085
if self._format._use_default_local_heads_to_fetch():
3086
# We recognise this format, and its heads-to-fetch implementation
3087
# is the default one (tip + tags). In this case it's cheaper to
3088
# just use the default implementation rather than a special RPC as
3089
# the tip and tags data is cached.
3090
return branch.Branch.heads_to_fetch(self)
3091
medium = self._client._medium
3092
if medium._is_remote_before((2, 4)):
3093
return self._vfs_heads_to_fetch()
3095
return self._rpc_heads_to_fetch()
3096
except errors.UnknownSmartMethod:
3097
medium._remember_remote_is_before((2, 4))
3098
return self._vfs_heads_to_fetch()
3100
def _rpc_heads_to_fetch(self):
3101
response = self._call('Branch.heads_to_fetch', self._remote_path())
3102
if len(response) != 2:
3103
raise errors.UnexpectedSmartServerResponse(response)
3104
must_fetch, if_present_fetch = response
3105
return set(must_fetch), set(if_present_fetch)
3107
def _vfs_heads_to_fetch(self):
3109
return self._real_branch.heads_to_fetch()
3112
class RemoteConfig(object):
3113
"""A Config that reads and writes from smart verbs.
3115
It is a low-level object that considers config data to be name/value pairs
3116
that may be associated with a section. Assigning meaning to the these
3117
values is done at higher levels like bzrlib.config.TreeConfig.
3120
def get_option(self, name, section=None, default=None):
3121
"""Return the value associated with a named option.
3123
:param name: The name of the value
3124
:param section: The section the option is in (if any)
3125
:param default: The value to return if the value is not set
3126
:return: The value or default value
3129
configobj = self._get_configobj()
3132
section_obj = configobj
3135
section_obj = configobj[section]
3138
if section_obj is None:
3141
value = section_obj.get(name, default)
3142
except errors.UnknownSmartMethod:
3143
value = self._vfs_get_option(name, section, default)
3144
for hook in config.OldConfigHooks['get']:
3145
hook(self, name, value)
3148
def _response_to_configobj(self, response):
3149
if len(response[0]) and response[0][0] != 'ok':
3150
raise errors.UnexpectedSmartServerResponse(response)
3151
lines = response[1].read_body_bytes().splitlines()
3152
conf = config.ConfigObj(lines, encoding='utf-8')
3153
for hook in config.OldConfigHooks['load']:
3158
class RemoteBranchConfig(RemoteConfig):
3159
"""A RemoteConfig for Branches."""
3161
def __init__(self, branch):
3162
self._branch = branch
3164
def _get_configobj(self):
3165
path = self._branch._remote_path()
3166
response = self._branch._client.call_expecting_body(
3167
'Branch.get_config_file', path)
3168
return self._response_to_configobj(response)
3170
def set_option(self, value, name, section=None):
3171
"""Set the value associated with a named option.
3173
:param value: The value to set
3174
:param name: The name of the value to set
3175
:param section: The section the option is in (if any)
3177
medium = self._branch._client._medium
3178
if medium._is_remote_before((1, 14)):
3179
return self._vfs_set_option(value, name, section)
3180
if isinstance(value, dict):
3181
if medium._is_remote_before((2, 2)):
3182
return self._vfs_set_option(value, name, section)
3183
return self._set_config_option_dict(value, name, section)
3185
return self._set_config_option(value, name, section)
3187
def _set_config_option(self, value, name, section):
3189
path = self._branch._remote_path()
3190
response = self._branch._client.call('Branch.set_config_option',
3191
path, self._branch._lock_token, self._branch._repo_lock_token,
3192
value.encode('utf8'), name, section or '')
3193
except errors.UnknownSmartMethod:
3194
medium = self._branch._client._medium
3195
medium._remember_remote_is_before((1, 14))
3196
return self._vfs_set_option(value, name, section)
3198
raise errors.UnexpectedSmartServerResponse(response)
3200
def _serialize_option_dict(self, option_dict):
3202
for key, value in option_dict.items():
3203
if isinstance(key, unicode):
3204
key = key.encode('utf8')
3205
if isinstance(value, unicode):
3206
value = value.encode('utf8')
3207
utf8_dict[key] = value
3208
return bencode.bencode(utf8_dict)
3210
def _set_config_option_dict(self, value, name, section):
3212
path = self._branch._remote_path()
3213
serialised_dict = self._serialize_option_dict(value)
3214
response = self._branch._client.call(
3215
'Branch.set_config_option_dict',
3216
path, self._branch._lock_token, self._branch._repo_lock_token,
3217
serialised_dict, name, section or '')
3218
except errors.UnknownSmartMethod:
3219
medium = self._branch._client._medium
3220
medium._remember_remote_is_before((2, 2))
3221
return self._vfs_set_option(value, name, section)
3223
raise errors.UnexpectedSmartServerResponse(response)
3225
def _real_object(self):
3226
self._branch._ensure_real()
3227
return self._branch._real_branch
3229
def _vfs_set_option(self, value, name, section=None):
3230
return self._real_object()._get_config().set_option(
3231
value, name, section)
3234
class RemoteBzrDirConfig(RemoteConfig):
3235
"""A RemoteConfig for BzrDirs."""
3237
def __init__(self, bzrdir):
3238
self._bzrdir = bzrdir
3240
def _get_configobj(self):
3241
medium = self._bzrdir._client._medium
3242
verb = 'BzrDir.get_config_file'
3243
if medium._is_remote_before((1, 15)):
3244
raise errors.UnknownSmartMethod(verb)
3245
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3246
response = self._bzrdir._call_expecting_body(
3248
return self._response_to_configobj(response)
3250
def _vfs_get_option(self, name, section, default):
3251
return self._real_object()._get_config().get_option(
3252
name, section, default)
3254
def set_option(self, value, name, section=None):
3255
"""Set the value associated with a named option.
3257
:param value: The value to set
3258
:param name: The name of the value to set
3259
:param section: The section the option is in (if any)
3261
return self._real_object()._get_config().set_option(
3262
value, name, section)
3264
def _real_object(self):
3265
self._bzrdir._ensure_real()
3266
return self._bzrdir._real_bzrdir
3270
def _extract_tar(tar, to_dir):
3271
"""Extract all the contents of a tarfile object.
3273
A replacement for extractall, which is not present in python2.4
3276
tar.extract(tarinfo, to_dir)
3279
def _translate_error(err, **context):
3280
"""Translate an ErrorFromSmartServer into a more useful error.
3282
Possible context keys:
3290
If the error from the server doesn't match a known pattern, then
3291
UnknownErrorFromSmartServer is raised.
3295
return context[name]
3296
except KeyError, key_err:
3297
mutter('Missing key %r in context %r', key_err.args[0], context)
3300
"""Get the path from the context if present, otherwise use first error
3304
return context['path']
3305
except KeyError, key_err:
3307
return err.error_args[0]
3308
except IndexError, idx_err:
3310
'Missing key %r in context %r', key_err.args[0], context)
3313
if err.error_verb == 'NoSuchRevision':
3314
raise NoSuchRevision(find('branch'), err.error_args[0])
3315
elif err.error_verb == 'nosuchrevision':
3316
raise NoSuchRevision(find('repository'), err.error_args[0])
3317
elif err.error_verb == 'nobranch':
3318
if len(err.error_args) >= 1:
3319
extra = err.error_args[0]
3322
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3324
elif err.error_verb == 'norepository':
3325
raise errors.NoRepositoryPresent(find('bzrdir'))
3326
elif err.error_verb == 'UnlockableTransport':
3327
raise errors.UnlockableTransport(find('bzrdir').root_transport)
3328
elif err.error_verb == 'TokenMismatch':
3329
raise errors.TokenMismatch(find('token'), '(remote token)')
3330
elif err.error_verb == 'Diverged':
3331
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3332
elif err.error_verb == 'NotStacked':
3333
raise errors.NotStacked(branch=find('branch'))
3334
elif err.error_verb == 'PermissionDenied':
3336
if len(err.error_args) >= 2:
3337
extra = err.error_args[1]
3340
raise errors.PermissionDenied(path, extra=extra)
3341
elif err.error_verb == 'ReadError':
3343
raise errors.ReadError(path)
3344
elif err.error_verb == 'NoSuchFile':
3346
raise errors.NoSuchFile(path)
3347
_translate_error_without_context(err)
3350
def _translate_error_without_context(err):
3351
"""Translate any ErrorFromSmartServer values that don't require context"""
3352
if err.error_verb == 'IncompatibleRepositories':
3353
raise errors.IncompatibleRepositories(err.error_args[0],
3354
err.error_args[1], err.error_args[2])
3355
elif err.error_verb == 'LockContention':
3356
raise errors.LockContention('(remote lock)')
3357
elif err.error_verb == 'LockFailed':
3358
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3359
elif err.error_verb == 'TipChangeRejected':
3360
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3361
elif err.error_verb == 'UnstackableBranchFormat':
3362
raise errors.UnstackableBranchFormat(*err.error_args)
3363
elif err.error_verb == 'UnstackableRepositoryFormat':
3364
raise errors.UnstackableRepositoryFormat(*err.error_args)
3365
elif err.error_verb == 'FileExists':
3366
raise errors.FileExists(err.error_args[0])
3367
elif err.error_verb == 'DirectoryNotEmpty':
3368
raise errors.DirectoryNotEmpty(err.error_args[0])
3369
elif err.error_verb == 'ShortReadvError':
3370
args = err.error_args
3371
raise errors.ShortReadvError(
3372
args[0], int(args[1]), int(args[2]), int(args[3]))
3373
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3374
encoding = str(err.error_args[0]) # encoding must always be a string
3375
val = err.error_args[1]
3376
start = int(err.error_args[2])
3377
end = int(err.error_args[3])
3378
reason = str(err.error_args[4]) # reason must always be a string
3379
if val.startswith('u:'):
3380
val = val[2:].decode('utf-8')
3381
elif val.startswith('s:'):
3382
val = val[2:].decode('base64')
3383
if err.error_verb == 'UnicodeDecodeError':
3384
raise UnicodeDecodeError(encoding, val, start, end, reason)
3385
elif err.error_verb == 'UnicodeEncodeError':
3386
raise UnicodeEncodeError(encoding, val, start, end, reason)
3387
elif err.error_verb == 'ReadOnlyError':
3388
raise errors.TransportNotPossible('readonly transport')
3389
elif err.error_verb == 'MemoryError':
3390
raise errors.BzrError("remote server out of memory\n"
3391
"Retry non-remotely, or contact the server admin for details.")
3392
raise errors.UnknownErrorFromSmartServer(err)