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.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.smart.client import _SmartClient
46
from bzrlib.revision import NULL_REVISION
47
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
48
from bzrlib.trace import mutter, note, warning
51
_DEFAULT_SEARCH_DEPTH = 100
54
class _RpcHelper(object):
55
"""Mixin class that helps with issuing RPCs."""
57
def _call(self, method, *args, **err_context):
59
return self._client.call(method, *args)
60
except errors.ErrorFromSmartServer, err:
61
self._translate_error(err, **err_context)
63
def _call_expecting_body(self, method, *args, **err_context):
65
return self._client.call_expecting_body(method, *args)
66
except errors.ErrorFromSmartServer, err:
67
self._translate_error(err, **err_context)
69
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
71
return self._client.call_with_body_bytes(method, args, body_bytes)
72
except errors.ErrorFromSmartServer, err:
73
self._translate_error(err, **err_context)
75
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
78
return self._client.call_with_body_bytes_expecting_body(
79
method, args, body_bytes)
80
except errors.ErrorFromSmartServer, err:
81
self._translate_error(err, **err_context)
84
def response_tuple_to_repo_format(response):
85
"""Convert a response tuple describing a repository format to a format."""
86
format = RemoteRepositoryFormat()
87
format._rich_root_data = (response[0] == 'yes')
88
format._supports_tree_reference = (response[1] == 'yes')
89
format._supports_external_lookups = (response[2] == 'yes')
90
format._network_name = response[3]
94
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
95
# does not have to be imported unless a remote format is involved.
97
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
98
"""Format representing bzrdirs accessed via a smart server"""
100
supports_workingtrees = False
103
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
104
# XXX: It's a bit ugly that the network name is here, because we'd
105
# like to believe that format objects are stateless or at least
106
# immutable, However, we do at least avoid mutating the name after
107
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
108
self._network_name = None
111
return "%s(_network_name=%r)" % (self.__class__.__name__,
114
def get_format_description(self):
115
if self._network_name:
116
real_format = controldir.network_format_registry.get(self._network_name)
117
return 'Remote: ' + real_format.get_format_description()
118
return 'bzr remote bzrdir'
120
def get_format_string(self):
121
raise NotImplementedError(self.get_format_string)
123
def network_name(self):
124
if self._network_name:
125
return self._network_name
127
raise AssertionError("No network name set.")
129
def initialize_on_transport(self, transport):
131
# hand off the request to the smart server
132
client_medium = transport.get_smart_medium()
133
except errors.NoSmartMedium:
134
# TODO: lookup the local format from a server hint.
135
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
136
return local_dir_format.initialize_on_transport(transport)
137
client = _SmartClient(client_medium)
138
path = client.remote_path_from_transport(transport)
140
response = client.call('BzrDirFormat.initialize', path)
141
except errors.ErrorFromSmartServer, err:
142
_translate_error(err, path=path)
143
if response[0] != 'ok':
144
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
145
format = RemoteBzrDirFormat()
146
self._supply_sub_formats_to(format)
147
return RemoteBzrDir(transport, format)
149
def parse_NoneTrueFalse(self, arg):
156
raise AssertionError("invalid arg %r" % arg)
158
def _serialize_NoneTrueFalse(self, arg):
165
def _serialize_NoneString(self, arg):
168
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
169
create_prefix=False, force_new_repo=False, stacked_on=None,
170
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
173
# hand off the request to the smart server
174
client_medium = transport.get_smart_medium()
175
except errors.NoSmartMedium:
178
# Decline to open it if the server doesn't support our required
179
# version (3) so that the VFS-based transport will do it.
180
if client_medium.should_probe():
182
server_version = client_medium.protocol_version()
183
if server_version != '2':
187
except errors.SmartProtocolError:
188
# Apparently there's no usable smart server there, even though
189
# the medium supports the smart protocol.
194
client = _SmartClient(client_medium)
195
path = client.remote_path_from_transport(transport)
196
if client_medium._is_remote_before((1, 16)):
199
# TODO: lookup the local format from a server hint.
200
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
201
self._supply_sub_formats_to(local_dir_format)
202
return local_dir_format.initialize_on_transport_ex(transport,
203
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
204
force_new_repo=force_new_repo, stacked_on=stacked_on,
205
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
206
make_working_trees=make_working_trees, shared_repo=shared_repo,
208
return self._initialize_on_transport_ex_rpc(client, path, transport,
209
use_existing_dir, create_prefix, force_new_repo, stacked_on,
210
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
212
def _initialize_on_transport_ex_rpc(self, client, path, transport,
213
use_existing_dir, create_prefix, force_new_repo, stacked_on,
214
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
216
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
217
args.append(self._serialize_NoneTrueFalse(create_prefix))
218
args.append(self._serialize_NoneTrueFalse(force_new_repo))
219
args.append(self._serialize_NoneString(stacked_on))
220
# stack_on_pwd is often/usually our transport
223
stack_on_pwd = transport.relpath(stack_on_pwd)
226
except errors.PathNotChild:
228
args.append(self._serialize_NoneString(stack_on_pwd))
229
args.append(self._serialize_NoneString(repo_format_name))
230
args.append(self._serialize_NoneTrueFalse(make_working_trees))
231
args.append(self._serialize_NoneTrueFalse(shared_repo))
232
request_network_name = self._network_name or \
233
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
235
response = client.call('BzrDirFormat.initialize_ex_1.16',
236
request_network_name, path, *args)
237
except errors.UnknownSmartMethod:
238
client._medium._remember_remote_is_before((1,16))
239
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
240
self._supply_sub_formats_to(local_dir_format)
241
return local_dir_format.initialize_on_transport_ex(transport,
242
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
243
force_new_repo=force_new_repo, stacked_on=stacked_on,
244
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
245
make_working_trees=make_working_trees, shared_repo=shared_repo,
247
except errors.ErrorFromSmartServer, err:
248
_translate_error(err, path=path)
249
repo_path = response[0]
250
bzrdir_name = response[6]
251
require_stacking = response[7]
252
require_stacking = self.parse_NoneTrueFalse(require_stacking)
253
format = RemoteBzrDirFormat()
254
format._network_name = bzrdir_name
255
self._supply_sub_formats_to(format)
256
bzrdir = RemoteBzrDir(transport, format, _client=client)
258
repo_format = response_tuple_to_repo_format(response[1:])
262
repo_bzrdir_format = RemoteBzrDirFormat()
263
repo_bzrdir_format._network_name = response[5]
264
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
268
final_stack = response[8] or None
269
final_stack_pwd = response[9] or None
271
final_stack_pwd = urlutils.join(
272
transport.base, final_stack_pwd)
273
remote_repo = RemoteRepository(repo_bzr, repo_format)
274
if len(response) > 10:
275
# Updated server verb that locks remotely.
276
repo_lock_token = response[10] or None
277
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
279
remote_repo.dont_leave_lock_in_place()
281
remote_repo.lock_write()
282
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
283
final_stack_pwd, require_stacking)
284
policy.acquire_repository()
288
bzrdir._format.set_branch_format(self.get_branch_format())
290
# The repo has already been created, but we need to make sure that
291
# we'll make a stackable branch.
292
bzrdir._format.require_stacking(_skip_repo=True)
293
return remote_repo, bzrdir, require_stacking, policy
295
def _open(self, transport):
296
return RemoteBzrDir(transport, self)
298
def __eq__(self, other):
299
if not isinstance(other, RemoteBzrDirFormat):
301
return self.get_format_description() == other.get_format_description()
303
def __return_repository_format(self):
304
# Always return a RemoteRepositoryFormat object, but if a specific bzr
305
# repository format has been asked for, tell the RemoteRepositoryFormat
306
# that it should use that for init() etc.
307
result = RemoteRepositoryFormat()
308
custom_format = getattr(self, '_repository_format', None)
310
if isinstance(custom_format, RemoteRepositoryFormat):
313
# We will use the custom format to create repositories over the
314
# wire; expose its details like rich_root_data for code to
316
result._custom_format = custom_format
319
def get_branch_format(self):
320
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
321
if not isinstance(result, RemoteBranchFormat):
322
new_result = RemoteBranchFormat()
323
new_result._custom_format = result
325
self.set_branch_format(new_result)
329
repository_format = property(__return_repository_format,
330
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
333
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
334
"""Control directory on a remote server, accessed via bzr:// or similar."""
336
def __init__(self, transport, format, _client=None, _force_probe=False):
337
"""Construct a RemoteBzrDir.
339
:param _client: Private parameter for testing. Disables probing and the
340
use of a real bzrdir.
342
_mod_bzrdir.BzrDir.__init__(self, transport, format)
343
# this object holds a delegated bzrdir that uses file-level operations
344
# to talk to the other side
345
self._real_bzrdir = None
346
self._has_working_tree = None
347
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
348
# create_branch for details.
349
self._next_open_branch_result = None
352
medium = transport.get_smart_medium()
353
self._client = client._SmartClient(medium)
355
self._client = _client
362
return '%s(%r)' % (self.__class__.__name__, self._client)
364
def _probe_bzrdir(self):
365
medium = self._client._medium
366
path = self._path_for_remote_call(self._client)
367
if medium._is_remote_before((2, 1)):
371
self._rpc_open_2_1(path)
373
except errors.UnknownSmartMethod:
374
medium._remember_remote_is_before((2, 1))
377
def _rpc_open_2_1(self, path):
378
response = self._call('BzrDir.open_2.1', path)
379
if response == ('no',):
380
raise errors.NotBranchError(path=self.root_transport.base)
381
elif response[0] == 'yes':
382
if response[1] == 'yes':
383
self._has_working_tree = True
384
elif response[1] == 'no':
385
self._has_working_tree = False
387
raise errors.UnexpectedSmartServerResponse(response)
389
raise errors.UnexpectedSmartServerResponse(response)
391
def _rpc_open(self, path):
392
response = self._call('BzrDir.open', path)
393
if response not in [('yes',), ('no',)]:
394
raise errors.UnexpectedSmartServerResponse(response)
395
if response == ('no',):
396
raise errors.NotBranchError(path=self.root_transport.base)
398
def _ensure_real(self):
399
"""Ensure that there is a _real_bzrdir set.
401
Used before calls to self._real_bzrdir.
403
if not self._real_bzrdir:
404
if 'hpssvfs' in debug.debug_flags:
406
warning('VFS BzrDir access triggered\n%s',
407
''.join(traceback.format_stack()))
408
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
409
self.root_transport, _server_formats=False)
410
self._format._network_name = \
411
self._real_bzrdir._format.network_name()
413
def _translate_error(self, err, **context):
414
_translate_error(err, bzrdir=self, **context)
416
def break_lock(self):
417
# Prevent aliasing problems in the next_open_branch_result cache.
418
# See create_branch for rationale.
419
self._next_open_branch_result = None
420
return _mod_bzrdir.BzrDir.break_lock(self)
422
def _vfs_cloning_metadir(self, require_stacking=False):
424
return self._real_bzrdir.cloning_metadir(
425
require_stacking=require_stacking)
427
def cloning_metadir(self, require_stacking=False):
428
medium = self._client._medium
429
if medium._is_remote_before((1, 13)):
430
return self._vfs_cloning_metadir(require_stacking=require_stacking)
431
verb = 'BzrDir.cloning_metadir'
436
path = self._path_for_remote_call(self._client)
438
response = self._call(verb, path, stacking)
439
except errors.UnknownSmartMethod:
440
medium._remember_remote_is_before((1, 13))
441
return self._vfs_cloning_metadir(require_stacking=require_stacking)
442
except errors.UnknownErrorFromSmartServer, err:
443
if err.error_tuple != ('BranchReference',):
445
# We need to resolve the branch reference to determine the
446
# cloning_metadir. This causes unnecessary RPCs to open the
447
# referenced branch (and bzrdir, etc) but only when the caller
448
# didn't already resolve the branch reference.
449
referenced_branch = self.open_branch()
450
return referenced_branch.bzrdir.cloning_metadir()
451
if len(response) != 3:
452
raise errors.UnexpectedSmartServerResponse(response)
453
control_name, repo_name, branch_info = response
454
if len(branch_info) != 2:
455
raise errors.UnexpectedSmartServerResponse(response)
456
branch_ref, branch_name = branch_info
457
format = controldir.network_format_registry.get(control_name)
459
format.repository_format = _mod_repository.network_format_registry.get(
461
if branch_ref == 'ref':
462
# XXX: we need possible_transports here to avoid reopening the
463
# connection to the referenced location
464
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
465
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
466
format.set_branch_format(branch_format)
467
elif branch_ref == 'branch':
469
format.set_branch_format(
470
branch.network_format_registry.get(branch_name))
472
raise errors.UnexpectedSmartServerResponse(response)
475
def create_repository(self, shared=False):
476
# as per meta1 formats - just delegate to the format object which may
478
result = self._format.repository_format.initialize(self, shared)
479
if not isinstance(result, RemoteRepository):
480
return self.open_repository()
484
def destroy_repository(self):
485
"""See BzrDir.destroy_repository"""
487
self._real_bzrdir.destroy_repository()
489
def create_branch(self, name=None, repository=None,
490
append_revisions_only=None):
491
# as per meta1 formats - just delegate to the format object which may
493
real_branch = self._format.get_branch_format().initialize(self,
494
name=name, repository=repository,
495
append_revisions_only=append_revisions_only)
496
if not isinstance(real_branch, RemoteBranch):
497
if not isinstance(repository, RemoteRepository):
498
raise AssertionError(
499
'need a RemoteRepository to use with RemoteBranch, got %r'
501
result = RemoteBranch(self, repository, real_branch, name=name)
504
# BzrDir.clone_on_transport() uses the result of create_branch but does
505
# not return it to its callers; we save approximately 8% of our round
506
# trips by handing the branch we created back to the first caller to
507
# open_branch rather than probing anew. Long term we need a API in
508
# bzrdir that doesn't discard result objects (like result_branch).
510
self._next_open_branch_result = result
513
def destroy_branch(self, name=None):
514
"""See BzrDir.destroy_branch"""
516
self._real_bzrdir.destroy_branch(name=name)
517
self._next_open_branch_result = None
519
def create_workingtree(self, revision_id=None, from_branch=None,
520
accelerator_tree=None, hardlink=False):
521
raise errors.NotLocalUrl(self.transport.base)
523
def find_branch_format(self, name=None):
524
"""Find the branch 'format' for this bzrdir.
526
This might be a synthetic object for e.g. RemoteBranch and SVN.
528
b = self.open_branch(name=name)
531
def get_branch_reference(self, name=None):
532
"""See BzrDir.get_branch_reference()."""
534
# XXX JRV20100304: Support opening colocated branches
535
raise errors.NoColocatedBranchSupport(self)
536
response = self._get_branch_reference()
537
if response[0] == 'ref':
542
def _get_branch_reference(self):
543
path = self._path_for_remote_call(self._client)
544
medium = self._client._medium
546
('BzrDir.open_branchV3', (2, 1)),
547
('BzrDir.open_branchV2', (1, 13)),
548
('BzrDir.open_branch', None),
550
for verb, required_version in candidate_calls:
551
if required_version and medium._is_remote_before(required_version):
554
response = self._call(verb, path)
555
except errors.UnknownSmartMethod:
556
if required_version is None:
558
medium._remember_remote_is_before(required_version)
561
if verb == 'BzrDir.open_branch':
562
if response[0] != 'ok':
563
raise errors.UnexpectedSmartServerResponse(response)
564
if response[1] != '':
565
return ('ref', response[1])
567
return ('branch', '')
568
if response[0] not in ('ref', 'branch'):
569
raise errors.UnexpectedSmartServerResponse(response)
572
def _get_tree_branch(self, name=None):
573
"""See BzrDir._get_tree_branch()."""
574
return None, self.open_branch(name=name)
576
def open_branch(self, name=None, unsupported=False,
577
ignore_fallbacks=False):
579
raise NotImplementedError('unsupported flag support not implemented yet.')
580
if self._next_open_branch_result is not None:
581
# See create_branch for details.
582
result = self._next_open_branch_result
583
self._next_open_branch_result = None
585
response = self._get_branch_reference()
586
if response[0] == 'ref':
587
# a branch reference, use the existing BranchReference logic.
588
format = BranchReferenceFormat()
589
return format.open(self, name=name, _found=True,
590
location=response[1], ignore_fallbacks=ignore_fallbacks)
591
branch_format_name = response[1]
592
if not branch_format_name:
593
branch_format_name = None
594
format = RemoteBranchFormat(network_name=branch_format_name)
595
return RemoteBranch(self, self.find_repository(), format=format,
596
setup_stacking=not ignore_fallbacks, name=name)
598
def _open_repo_v1(self, path):
599
verb = 'BzrDir.find_repository'
600
response = self._call(verb, path)
601
if response[0] != 'ok':
602
raise errors.UnexpectedSmartServerResponse(response)
603
# servers that only support the v1 method don't support external
606
repo = self._real_bzrdir.open_repository()
607
response = response + ('no', repo._format.network_name())
608
return response, repo
610
def _open_repo_v2(self, path):
611
verb = 'BzrDir.find_repositoryV2'
612
response = self._call(verb, path)
613
if response[0] != 'ok':
614
raise errors.UnexpectedSmartServerResponse(response)
616
repo = self._real_bzrdir.open_repository()
617
response = response + (repo._format.network_name(),)
618
return response, repo
620
def _open_repo_v3(self, path):
621
verb = 'BzrDir.find_repositoryV3'
622
medium = self._client._medium
623
if medium._is_remote_before((1, 13)):
624
raise errors.UnknownSmartMethod(verb)
626
response = self._call(verb, path)
627
except errors.UnknownSmartMethod:
628
medium._remember_remote_is_before((1, 13))
630
if response[0] != 'ok':
631
raise errors.UnexpectedSmartServerResponse(response)
632
return response, None
634
def open_repository(self):
635
path = self._path_for_remote_call(self._client)
637
for probe in [self._open_repo_v3, self._open_repo_v2,
640
response, real_repo = probe(path)
642
except errors.UnknownSmartMethod:
645
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
646
if response[0] != 'ok':
647
raise errors.UnexpectedSmartServerResponse(response)
648
if len(response) != 6:
649
raise SmartProtocolError('incorrect response length %s' % (response,))
650
if response[1] == '':
651
# repo is at this dir.
652
format = response_tuple_to_repo_format(response[2:])
653
# Used to support creating a real format instance when needed.
654
format._creating_bzrdir = self
655
remote_repo = RemoteRepository(self, format)
656
format._creating_repo = remote_repo
657
if real_repo is not None:
658
remote_repo._set_real_repository(real_repo)
661
raise errors.NoRepositoryPresent(self)
663
def has_workingtree(self):
664
if self._has_working_tree is None:
666
self._has_working_tree = self._real_bzrdir.has_workingtree()
667
return self._has_working_tree
669
def open_workingtree(self, recommend_upgrade=True):
670
if self.has_workingtree():
671
raise errors.NotLocalUrl(self.root_transport)
673
raise errors.NoWorkingTree(self.root_transport.base)
675
def _path_for_remote_call(self, client):
676
"""Return the path to be used for this bzrdir in a remote call."""
677
return urlutils.split_segment_parameters_raw(
678
client.remote_path_from_transport(self.root_transport))[0]
680
def get_branch_transport(self, branch_format, name=None):
682
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
684
def get_repository_transport(self, repository_format):
686
return self._real_bzrdir.get_repository_transport(repository_format)
688
def get_workingtree_transport(self, workingtree_format):
690
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
692
def can_convert_format(self):
693
"""Upgrading of remote bzrdirs is not supported yet."""
696
def needs_format_conversion(self, format):
697
"""Upgrading of remote bzrdirs is not supported yet."""
700
def clone(self, url, revision_id=None, force_new_repo=False,
701
preserve_stacking=False):
703
return self._real_bzrdir.clone(url, revision_id=revision_id,
704
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
706
def _get_config(self):
707
return RemoteBzrDirConfig(self)
710
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
711
"""Format for repositories accessed over a _SmartClient.
713
Instances of this repository are represented by RemoteRepository
716
The RemoteRepositoryFormat is parameterized during construction
717
to reflect the capabilities of the real, remote format. Specifically
718
the attributes rich_root_data and supports_tree_reference are set
719
on a per instance basis, and are not set (and should not be) at
722
:ivar _custom_format: If set, a specific concrete repository format that
723
will be used when initializing a repository with this
724
RemoteRepositoryFormat.
725
:ivar _creating_repo: If set, the repository object that this
726
RemoteRepositoryFormat was created for: it can be called into
727
to obtain data like the network name.
730
_matchingbzrdir = RemoteBzrDirFormat()
731
supports_full_versioned_files = True
732
supports_leaving_lock = True
735
_mod_repository.RepositoryFormat.__init__(self)
736
self._custom_format = None
737
self._network_name = None
738
self._creating_bzrdir = None
739
self._revision_graph_can_have_wrong_parents = None
740
self._supports_chks = None
741
self._supports_external_lookups = None
742
self._supports_tree_reference = None
743
self._supports_funky_characters = None
744
self._rich_root_data = None
747
return "%s(_network_name=%r)" % (self.__class__.__name__,
751
def fast_deltas(self):
753
return self._custom_format.fast_deltas
756
def rich_root_data(self):
757
if self._rich_root_data is None:
759
self._rich_root_data = self._custom_format.rich_root_data
760
return self._rich_root_data
763
def supports_chks(self):
764
if self._supports_chks is None:
766
self._supports_chks = self._custom_format.supports_chks
767
return self._supports_chks
770
def supports_external_lookups(self):
771
if self._supports_external_lookups is None:
773
self._supports_external_lookups = \
774
self._custom_format.supports_external_lookups
775
return self._supports_external_lookups
778
def supports_funky_characters(self):
779
if self._supports_funky_characters is None:
781
self._supports_funky_characters = \
782
self._custom_format.supports_funky_characters
783
return self._supports_funky_characters
786
def supports_tree_reference(self):
787
if self._supports_tree_reference is None:
789
self._supports_tree_reference = \
790
self._custom_format.supports_tree_reference
791
return self._supports_tree_reference
794
def revision_graph_can_have_wrong_parents(self):
795
if self._revision_graph_can_have_wrong_parents is None:
797
self._revision_graph_can_have_wrong_parents = \
798
self._custom_format.revision_graph_can_have_wrong_parents
799
return self._revision_graph_can_have_wrong_parents
801
def _vfs_initialize(self, a_bzrdir, shared):
802
"""Helper for common code in initialize."""
803
if self._custom_format:
804
# Custom format requested
805
result = self._custom_format.initialize(a_bzrdir, shared=shared)
806
elif self._creating_bzrdir is not None:
807
# Use the format that the repository we were created to back
809
prior_repo = self._creating_bzrdir.open_repository()
810
prior_repo._ensure_real()
811
result = prior_repo._real_repository._format.initialize(
812
a_bzrdir, shared=shared)
814
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
815
# support remote initialization.
816
# We delegate to a real object at this point (as RemoteBzrDir
817
# delegate to the repository format which would lead to infinite
818
# recursion if we just called a_bzrdir.create_repository.
819
a_bzrdir._ensure_real()
820
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
821
if not isinstance(result, RemoteRepository):
822
return self.open(a_bzrdir)
826
def initialize(self, a_bzrdir, shared=False):
827
# Being asked to create on a non RemoteBzrDir:
828
if not isinstance(a_bzrdir, RemoteBzrDir):
829
return self._vfs_initialize(a_bzrdir, shared)
830
medium = a_bzrdir._client._medium
831
if medium._is_remote_before((1, 13)):
832
return self._vfs_initialize(a_bzrdir, shared)
833
# Creating on a remote bzr dir.
834
# 1) get the network name to use.
835
if self._custom_format:
836
network_name = self._custom_format.network_name()
837
elif self._network_name:
838
network_name = self._network_name
840
# Select the current bzrlib default and ask for that.
841
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
842
reference_format = reference_bzrdir_format.repository_format
843
network_name = reference_format.network_name()
844
# 2) try direct creation via RPC
845
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
846
verb = 'BzrDir.create_repository'
852
response = a_bzrdir._call(verb, path, network_name, shared_str)
853
except errors.UnknownSmartMethod:
854
# Fallback - use vfs methods
855
medium._remember_remote_is_before((1, 13))
856
return self._vfs_initialize(a_bzrdir, shared)
858
# Turn the response into a RemoteRepository object.
859
format = response_tuple_to_repo_format(response[1:])
860
# Used to support creating a real format instance when needed.
861
format._creating_bzrdir = a_bzrdir
862
remote_repo = RemoteRepository(a_bzrdir, format)
863
format._creating_repo = remote_repo
866
def open(self, a_bzrdir):
867
if not isinstance(a_bzrdir, RemoteBzrDir):
868
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
869
return a_bzrdir.open_repository()
871
def _ensure_real(self):
872
if self._custom_format is None:
873
self._custom_format = _mod_repository.network_format_registry.get(
877
def _fetch_order(self):
879
return self._custom_format._fetch_order
882
def _fetch_uses_deltas(self):
884
return self._custom_format._fetch_uses_deltas
887
def _fetch_reconcile(self):
889
return self._custom_format._fetch_reconcile
891
def get_format_description(self):
893
return 'Remote: ' + self._custom_format.get_format_description()
895
def __eq__(self, other):
896
return self.__class__ is other.__class__
898
def network_name(self):
899
if self._network_name:
900
return self._network_name
901
self._creating_repo._ensure_real()
902
return self._creating_repo._real_repository._format.network_name()
905
def pack_compresses(self):
907
return self._custom_format.pack_compresses
910
def _serializer(self):
912
return self._custom_format._serializer
915
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
916
controldir.ControlComponent):
917
"""Repository accessed over rpc.
919
For the moment most operations are performed using local transport-backed
923
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
924
"""Create a RemoteRepository instance.
926
:param remote_bzrdir: The bzrdir hosting this repository.
927
:param format: The RemoteFormat object to use.
928
:param real_repository: If not None, a local implementation of the
929
repository logic for the repository, usually accessing the data
931
:param _client: Private testing parameter - override the smart client
932
to be used by the repository.
935
self._real_repository = real_repository
937
self._real_repository = None
938
self.bzrdir = remote_bzrdir
940
self._client = remote_bzrdir._client
942
self._client = _client
943
self._format = format
944
self._lock_mode = None
945
self._lock_token = None
947
self._leave_lock = False
948
# Cache of revision parents; misses are cached during read locks, and
949
# write locks when no _real_repository has been set.
950
self._unstacked_provider = graph.CachingParentsProvider(
951
get_parent_map=self._get_parent_map_rpc)
952
self._unstacked_provider.disable_cache()
954
# These depend on the actual remote format, so force them off for
955
# maximum compatibility. XXX: In future these should depend on the
956
# remote repository instance, but this is irrelevant until we perform
957
# reconcile via an RPC call.
958
self._reconcile_does_inventory_gc = False
959
self._reconcile_fixes_text_parents = False
960
self._reconcile_backsup_inventory = False
961
self.base = self.bzrdir.transport.base
962
# Additional places to query for data.
963
self._fallback_repositories = []
966
def user_transport(self):
967
return self.bzrdir.user_transport
970
def control_transport(self):
971
# XXX: Normally you shouldn't directly get at the remote repository
972
# transport, but I'm not sure it's worth making this method
973
# optional -- mbp 2010-04-21
974
return self.bzrdir.get_repository_transport(None)
977
return "%s(%s)" % (self.__class__.__name__, self.base)
981
def abort_write_group(self, suppress_errors=False):
982
"""Complete a write group on the decorated repository.
984
Smart methods perform operations in a single step so this API
985
is not really applicable except as a compatibility thunk
986
for older plugins that don't use e.g. the CommitBuilder
989
:param suppress_errors: see Repository.abort_write_group.
992
return self._real_repository.abort_write_group(
993
suppress_errors=suppress_errors)
997
"""Decorate the real repository for now.
999
In the long term a full blown network facility is needed to avoid
1000
creating a real repository object locally.
1003
return self._real_repository.chk_bytes
1005
def commit_write_group(self):
1006
"""Complete a write group on the decorated repository.
1008
Smart methods perform operations in a single step so this API
1009
is not really applicable except as a compatibility thunk
1010
for older plugins that don't use e.g. the CommitBuilder
1014
return self._real_repository.commit_write_group()
1016
def resume_write_group(self, tokens):
1018
return self._real_repository.resume_write_group(tokens)
1020
def suspend_write_group(self):
1022
return self._real_repository.suspend_write_group()
1024
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1026
return self._real_repository.get_missing_parent_inventories(
1027
check_for_missing_texts=check_for_missing_texts)
1029
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1031
return self._real_repository.get_rev_id_for_revno(
1034
def get_rev_id_for_revno(self, revno, known_pair):
1035
"""See Repository.get_rev_id_for_revno."""
1036
path = self.bzrdir._path_for_remote_call(self._client)
1038
if self._client._medium._is_remote_before((1, 17)):
1039
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1040
response = self._call(
1041
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1042
except errors.UnknownSmartMethod:
1043
self._client._medium._remember_remote_is_before((1, 17))
1044
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1045
if response[0] == 'ok':
1046
return True, response[1]
1047
elif response[0] == 'history-incomplete':
1048
known_pair = response[1:3]
1049
for fallback in self._fallback_repositories:
1050
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1055
# Not found in any fallbacks
1056
return False, known_pair
1058
raise errors.UnexpectedSmartServerResponse(response)
1060
def _ensure_real(self):
1061
"""Ensure that there is a _real_repository set.
1063
Used before calls to self._real_repository.
1065
Note that _ensure_real causes many roundtrips to the server which are
1066
not desirable, and prevents the use of smart one-roundtrip RPC's to
1067
perform complex operations (such as accessing parent data, streaming
1068
revisions etc). Adding calls to _ensure_real should only be done when
1069
bringing up new functionality, adding fallbacks for smart methods that
1070
require a fallback path, and never to replace an existing smart method
1071
invocation. If in doubt chat to the bzr network team.
1073
if self._real_repository is None:
1074
if 'hpssvfs' in debug.debug_flags:
1076
warning('VFS Repository access triggered\n%s',
1077
''.join(traceback.format_stack()))
1078
self._unstacked_provider.missing_keys.clear()
1079
self.bzrdir._ensure_real()
1080
self._set_real_repository(
1081
self.bzrdir._real_bzrdir.open_repository())
1083
def _translate_error(self, err, **context):
1084
self.bzrdir._translate_error(err, repository=self, **context)
1086
def find_text_key_references(self):
1087
"""Find the text key references within the repository.
1089
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1090
to whether they were referred to by the inventory of the
1091
revision_id that they contain. The inventory texts from all present
1092
revision ids are assessed to generate this report.
1095
return self._real_repository.find_text_key_references()
1097
def _generate_text_key_index(self):
1098
"""Generate a new text key index for the repository.
1100
This is an expensive function that will take considerable time to run.
1102
:return: A dict mapping (file_id, revision_id) tuples to a list of
1103
parents, also (file_id, revision_id) tuples.
1106
return self._real_repository._generate_text_key_index()
1108
def _get_revision_graph(self, revision_id):
1109
"""Private method for using with old (< 1.2) servers to fallback."""
1110
if revision_id is None:
1112
elif _mod_revision.is_null(revision_id):
1115
path = self.bzrdir._path_for_remote_call(self._client)
1116
response = self._call_expecting_body(
1117
'Repository.get_revision_graph', path, revision_id)
1118
response_tuple, response_handler = response
1119
if response_tuple[0] != 'ok':
1120
raise errors.UnexpectedSmartServerResponse(response_tuple)
1121
coded = response_handler.read_body_bytes()
1123
# no revisions in this repository!
1125
lines = coded.split('\n')
1128
d = tuple(line.split())
1129
revision_graph[d[0]] = d[1:]
1131
return revision_graph
1133
def _get_sink(self):
1134
"""See Repository._get_sink()."""
1135
return RemoteStreamSink(self)
1137
def _get_source(self, to_format):
1138
"""Return a source for streaming from this repository."""
1139
return RemoteStreamSource(self, to_format)
1142
def get_file_graph(self):
1143
return graph.Graph(self.texts)
1146
def has_revision(self, revision_id):
1147
"""True if this repository has a copy of the revision."""
1148
# Copy of bzrlib.repository.Repository.has_revision
1149
return revision_id in self.has_revisions((revision_id,))
1152
def has_revisions(self, revision_ids):
1153
"""Probe to find out the presence of multiple revisions.
1155
:param revision_ids: An iterable of revision_ids.
1156
:return: A set of the revision_ids that were present.
1158
# Copy of bzrlib.repository.Repository.has_revisions
1159
parent_map = self.get_parent_map(revision_ids)
1160
result = set(parent_map)
1161
if _mod_revision.NULL_REVISION in revision_ids:
1162
result.add(_mod_revision.NULL_REVISION)
1165
def _has_same_fallbacks(self, other_repo):
1166
"""Returns true if the repositories have the same fallbacks."""
1167
# XXX: copied from Repository; it should be unified into a base class
1168
# <https://bugs.launchpad.net/bzr/+bug/401622>
1169
my_fb = self._fallback_repositories
1170
other_fb = other_repo._fallback_repositories
1171
if len(my_fb) != len(other_fb):
1173
for f, g in zip(my_fb, other_fb):
1174
if not f.has_same_location(g):
1178
def has_same_location(self, other):
1179
# TODO: Move to RepositoryBase and unify with the regular Repository
1180
# one; unfortunately the tests rely on slightly different behaviour at
1181
# present -- mbp 20090710
1182
return (self.__class__ is other.__class__ and
1183
self.bzrdir.transport.base == other.bzrdir.transport.base)
1185
def get_graph(self, other_repository=None):
1186
"""Return the graph for this repository format"""
1187
parents_provider = self._make_parents_provider(other_repository)
1188
return graph.Graph(parents_provider)
1191
def get_known_graph_ancestry(self, revision_ids):
1192
"""Return the known graph for a set of revision ids and their ancestors.
1194
st = static_tuple.StaticTuple
1195
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1196
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1197
return graph.GraphThunkIdsToKeys(known_graph)
1199
def gather_stats(self, revid=None, committers=None):
1200
"""See Repository.gather_stats()."""
1201
path = self.bzrdir._path_for_remote_call(self._client)
1202
# revid can be None to indicate no revisions, not just NULL_REVISION
1203
if revid is None or _mod_revision.is_null(revid):
1207
if committers is None or not committers:
1208
fmt_committers = 'no'
1210
fmt_committers = 'yes'
1211
response_tuple, response_handler = self._call_expecting_body(
1212
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1213
if response_tuple[0] != 'ok':
1214
raise errors.UnexpectedSmartServerResponse(response_tuple)
1216
body = response_handler.read_body_bytes()
1218
for line in body.split('\n'):
1221
key, val_text = line.split(':')
1222
if key in ('revisions', 'size', 'committers'):
1223
result[key] = int(val_text)
1224
elif key in ('firstrev', 'latestrev'):
1225
values = val_text.split(' ')[1:]
1226
result[key] = (float(values[0]), long(values[1]))
1230
def find_branches(self, using=False):
1231
"""See Repository.find_branches()."""
1232
# should be an API call to the server.
1234
return self._real_repository.find_branches(using=using)
1236
def get_physical_lock_status(self):
1237
"""See Repository.get_physical_lock_status()."""
1238
# should be an API call to the server.
1240
return self._real_repository.get_physical_lock_status()
1242
def is_in_write_group(self):
1243
"""Return True if there is an open write group.
1245
write groups are only applicable locally for the smart server..
1247
if self._real_repository:
1248
return self._real_repository.is_in_write_group()
1250
def is_locked(self):
1251
return self._lock_count >= 1
1253
def is_shared(self):
1254
"""See Repository.is_shared()."""
1255
path = self.bzrdir._path_for_remote_call(self._client)
1256
response = self._call('Repository.is_shared', path)
1257
if response[0] not in ('yes', 'no'):
1258
raise SmartProtocolError('unexpected response code %s' % (response,))
1259
return response[0] == 'yes'
1261
def is_write_locked(self):
1262
return self._lock_mode == 'w'
1264
def _warn_if_deprecated(self, branch=None):
1265
# If we have a real repository, the check will be done there, if we
1266
# don't the check will be done remotely.
1269
def lock_read(self):
1270
"""Lock the repository for read operations.
1272
:return: A bzrlib.lock.LogicalLockResult.
1274
# wrong eventually - want a local lock cache context
1275
if not self._lock_mode:
1276
self._note_lock('r')
1277
self._lock_mode = 'r'
1278
self._lock_count = 1
1279
self._unstacked_provider.enable_cache(cache_misses=True)
1280
if self._real_repository is not None:
1281
self._real_repository.lock_read()
1282
for repo in self._fallback_repositories:
1285
self._lock_count += 1
1286
return lock.LogicalLockResult(self.unlock)
1288
def _remote_lock_write(self, token):
1289
path = self.bzrdir._path_for_remote_call(self._client)
1292
err_context = {'token': token}
1293
response = self._call('Repository.lock_write', path, token,
1295
if response[0] == 'ok':
1296
ok, token = response
1299
raise errors.UnexpectedSmartServerResponse(response)
1301
def lock_write(self, token=None, _skip_rpc=False):
1302
if not self._lock_mode:
1303
self._note_lock('w')
1305
if self._lock_token is not None:
1306
if token != self._lock_token:
1307
raise errors.TokenMismatch(token, self._lock_token)
1308
self._lock_token = token
1310
self._lock_token = self._remote_lock_write(token)
1311
# if self._lock_token is None, then this is something like packs or
1312
# svn where we don't get to lock the repo, or a weave style repository
1313
# where we cannot lock it over the wire and attempts to do so will
1315
if self._real_repository is not None:
1316
self._real_repository.lock_write(token=self._lock_token)
1317
if token is not None:
1318
self._leave_lock = True
1320
self._leave_lock = False
1321
self._lock_mode = 'w'
1322
self._lock_count = 1
1323
cache_misses = self._real_repository is None
1324
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1325
for repo in self._fallback_repositories:
1326
# Writes don't affect fallback repos
1328
elif self._lock_mode == 'r':
1329
raise errors.ReadOnlyError(self)
1331
self._lock_count += 1
1332
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1334
def leave_lock_in_place(self):
1335
if not self._lock_token:
1336
raise NotImplementedError(self.leave_lock_in_place)
1337
self._leave_lock = True
1339
def dont_leave_lock_in_place(self):
1340
if not self._lock_token:
1341
raise NotImplementedError(self.dont_leave_lock_in_place)
1342
self._leave_lock = False
1344
def _set_real_repository(self, repository):
1345
"""Set the _real_repository for this repository.
1347
:param repository: The repository to fallback to for non-hpss
1348
implemented operations.
1350
if self._real_repository is not None:
1351
# Replacing an already set real repository.
1352
# We cannot do this [currently] if the repository is locked -
1353
# synchronised state might be lost.
1354
if self.is_locked():
1355
raise AssertionError('_real_repository is already set')
1356
if isinstance(repository, RemoteRepository):
1357
raise AssertionError()
1358
self._real_repository = repository
1359
# three code paths happen here:
1360
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1361
# up stacking. In this case self._fallback_repositories is [], and the
1362
# real repo is already setup. Preserve the real repo and
1363
# RemoteRepository.add_fallback_repository will avoid adding
1365
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1366
# ensure_real is triggered from a branch, the real repository to
1367
# set already has a matching list with separate instances, but
1368
# as they are also RemoteRepositories we don't worry about making the
1369
# lists be identical.
1370
# 3) new servers, RemoteRepository.ensure_real is triggered before
1371
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1372
# and need to populate it.
1373
if (self._fallback_repositories and
1374
len(self._real_repository._fallback_repositories) !=
1375
len(self._fallback_repositories)):
1376
if len(self._real_repository._fallback_repositories):
1377
raise AssertionError(
1378
"cannot cleanly remove existing _fallback_repositories")
1379
for fb in self._fallback_repositories:
1380
self._real_repository.add_fallback_repository(fb)
1381
if self._lock_mode == 'w':
1382
# if we are already locked, the real repository must be able to
1383
# acquire the lock with our token.
1384
self._real_repository.lock_write(self._lock_token)
1385
elif self._lock_mode == 'r':
1386
self._real_repository.lock_read()
1388
def start_write_group(self):
1389
"""Start a write group on the decorated repository.
1391
Smart methods perform operations in a single step so this API
1392
is not really applicable except as a compatibility thunk
1393
for older plugins that don't use e.g. the CommitBuilder
1397
return self._real_repository.start_write_group()
1399
def _unlock(self, token):
1400
path = self.bzrdir._path_for_remote_call(self._client)
1402
# with no token the remote repository is not persistently locked.
1404
err_context = {'token': token}
1405
response = self._call('Repository.unlock', path, token,
1407
if response == ('ok',):
1410
raise errors.UnexpectedSmartServerResponse(response)
1412
@only_raises(errors.LockNotHeld, errors.LockBroken)
1414
if not self._lock_count:
1415
return lock.cant_unlock_not_held(self)
1416
self._lock_count -= 1
1417
if self._lock_count > 0:
1419
self._unstacked_provider.disable_cache()
1420
old_mode = self._lock_mode
1421
self._lock_mode = None
1423
# The real repository is responsible at present for raising an
1424
# exception if it's in an unfinished write group. However, it
1425
# normally will *not* actually remove the lock from disk - that's
1426
# done by the server on receiving the Repository.unlock call.
1427
# This is just to let the _real_repository stay up to date.
1428
if self._real_repository is not None:
1429
self._real_repository.unlock()
1431
# The rpc-level lock should be released even if there was a
1432
# problem releasing the vfs-based lock.
1434
# Only write-locked repositories need to make a remote method
1435
# call to perform the unlock.
1436
old_token = self._lock_token
1437
self._lock_token = None
1438
if not self._leave_lock:
1439
self._unlock(old_token)
1440
# Fallbacks are always 'lock_read()' so we don't pay attention to
1442
for repo in self._fallback_repositories:
1445
def break_lock(self):
1446
# should hand off to the network
1448
return self._real_repository.break_lock()
1450
def _get_tarball(self, compression):
1451
"""Return a TemporaryFile containing a repository tarball.
1453
Returns None if the server does not support sending tarballs.
1456
path = self.bzrdir._path_for_remote_call(self._client)
1458
response, protocol = self._call_expecting_body(
1459
'Repository.tarball', path, compression)
1460
except errors.UnknownSmartMethod:
1461
protocol.cancel_read_body()
1463
if response[0] == 'ok':
1464
# Extract the tarball and return it
1465
t = tempfile.NamedTemporaryFile()
1466
# TODO: rpc layer should read directly into it...
1467
t.write(protocol.read_body_bytes())
1470
raise errors.UnexpectedSmartServerResponse(response)
1472
def sprout(self, to_bzrdir, revision_id=None):
1473
# TODO: Option to control what format is created?
1475
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1477
dest_repo.fetch(self, revision_id=revision_id)
1480
### These methods are just thin shims to the VFS object for now.
1482
def revision_tree(self, revision_id):
1484
return self._real_repository.revision_tree(revision_id)
1486
def get_serializer_format(self):
1488
return self._real_repository.get_serializer_format()
1490
def get_commit_builder(self, branch, parents, config, timestamp=None,
1491
timezone=None, committer=None, revprops=None,
1492
revision_id=None, lossy=False):
1493
# FIXME: It ought to be possible to call this without immediately
1494
# triggering _ensure_real. For now it's the easiest thing to do.
1496
real_repo = self._real_repository
1497
builder = real_repo.get_commit_builder(branch, parents,
1498
config, timestamp=timestamp, timezone=timezone,
1499
committer=committer, revprops=revprops,
1500
revision_id=revision_id, lossy=lossy)
1503
def add_fallback_repository(self, repository):
1504
"""Add a repository to use for looking up data not held locally.
1506
:param repository: A repository.
1508
if not self._format.supports_external_lookups:
1509
raise errors.UnstackableRepositoryFormat(
1510
self._format.network_name(), self.base)
1511
# We need to accumulate additional repositories here, to pass them in
1514
# Make the check before we lock: this raises an exception.
1515
self._check_fallback_repository(repository)
1516
if self.is_locked():
1517
# We will call fallback.unlock() when we transition to the unlocked
1518
# state, so always add a lock here. If a caller passes us a locked
1519
# repository, they are responsible for unlocking it later.
1520
repository.lock_read()
1521
self._fallback_repositories.append(repository)
1522
# If self._real_repository was parameterised already (e.g. because a
1523
# _real_branch had its get_stacked_on_url method called), then the
1524
# repository to be added may already be in the _real_repositories list.
1525
if self._real_repository is not None:
1526
fallback_locations = [repo.user_url for repo in
1527
self._real_repository._fallback_repositories]
1528
if repository.user_url not in fallback_locations:
1529
self._real_repository.add_fallback_repository(repository)
1531
def _check_fallback_repository(self, repository):
1532
"""Check that this repository can fallback to repository safely.
1534
Raise an error if not.
1536
:param repository: A repository to fallback to.
1538
return _mod_repository.InterRepository._assert_same_model(
1541
def add_inventory(self, revid, inv, parents):
1543
return self._real_repository.add_inventory(revid, inv, parents)
1545
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1546
parents, basis_inv=None, propagate_caches=False):
1548
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1549
delta, new_revision_id, parents, basis_inv=basis_inv,
1550
propagate_caches=propagate_caches)
1552
def add_revision(self, rev_id, rev, inv=None, config=None):
1554
return self._real_repository.add_revision(
1555
rev_id, rev, inv=inv, config=config)
1558
def get_inventory(self, revision_id):
1560
return self._real_repository.get_inventory(revision_id)
1562
def iter_inventories(self, revision_ids, ordering=None):
1564
return self._real_repository.iter_inventories(revision_ids, ordering)
1567
def get_revision(self, revision_id):
1569
return self._real_repository.get_revision(revision_id)
1571
def get_transaction(self):
1573
return self._real_repository.get_transaction()
1576
def clone(self, a_bzrdir, revision_id=None):
1578
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1580
def make_working_trees(self):
1581
"""See Repository.make_working_trees"""
1583
return self._real_repository.make_working_trees()
1585
def refresh_data(self):
1586
"""Re-read any data needed to synchronise with disk.
1588
This method is intended to be called after another repository instance
1589
(such as one used by a smart server) has inserted data into the
1590
repository. On all repositories this will work outside of write groups.
1591
Some repository formats (pack and newer for bzrlib native formats)
1592
support refresh_data inside write groups. If called inside a write
1593
group on a repository that does not support refreshing in a write group
1594
IsInWriteGroupError will be raised.
1596
if self._real_repository is not None:
1597
self._real_repository.refresh_data()
1599
def revision_ids_to_search_result(self, result_set):
1600
"""Convert a set of revision ids to a graph SearchResult."""
1601
result_parents = set()
1602
for parents in self.get_graph().get_parent_map(
1603
result_set).itervalues():
1604
result_parents.update(parents)
1605
included_keys = result_set.intersection(result_parents)
1606
start_keys = result_set.difference(included_keys)
1607
exclude_keys = result_parents.difference(result_set)
1608
result = graph.SearchResult(start_keys, exclude_keys,
1609
len(result_set), result_set)
1613
def search_missing_revision_ids(self, other,
1614
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1615
find_ghosts=True, revision_ids=None, if_present_ids=None,
1617
"""Return the revision ids that other has that this does not.
1619
These are returned in topological order.
1621
revision_id: only return revision ids included by revision_id.
1623
if symbol_versioning.deprecated_passed(revision_id):
1624
symbol_versioning.warn(
1625
'search_missing_revision_ids(revision_id=...) was '
1626
'deprecated in 2.4. Use revision_ids=[...] instead.',
1627
DeprecationWarning, stacklevel=2)
1628
if revision_ids is not None:
1629
raise AssertionError(
1630
'revision_ids is mutually exclusive with revision_id')
1631
if revision_id is not None:
1632
revision_ids = [revision_id]
1633
inter_repo = _mod_repository.InterRepository.get(other, self)
1634
return inter_repo.search_missing_revision_ids(
1635
find_ghosts=find_ghosts, revision_ids=revision_ids,
1636
if_present_ids=if_present_ids, limit=limit)
1638
def fetch(self, source, revision_id=None, find_ghosts=False,
1640
# No base implementation to use as RemoteRepository is not a subclass
1641
# of Repository; so this is a copy of Repository.fetch().
1642
if fetch_spec is not None and revision_id is not None:
1643
raise AssertionError(
1644
"fetch_spec and revision_id are mutually exclusive.")
1645
if self.is_in_write_group():
1646
raise errors.InternalBzrError(
1647
"May not fetch while in a write group.")
1648
# fast path same-url fetch operations
1649
if (self.has_same_location(source)
1650
and fetch_spec is None
1651
and self._has_same_fallbacks(source)):
1652
# check that last_revision is in 'from' and then return a
1654
if (revision_id is not None and
1655
not _mod_revision.is_null(revision_id)):
1656
self.get_revision(revision_id)
1658
# if there is no specific appropriate InterRepository, this will get
1659
# the InterRepository base class, which raises an
1660
# IncompatibleRepositories when asked to fetch.
1661
inter = _mod_repository.InterRepository.get(source, self)
1662
return inter.fetch(revision_id=revision_id,
1663
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1665
def create_bundle(self, target, base, fileobj, format=None):
1667
self._real_repository.create_bundle(target, base, fileobj, format)
1670
@symbol_versioning.deprecated_method(
1671
symbol_versioning.deprecated_in((2, 4, 0)))
1672
def get_ancestry(self, revision_id, topo_sorted=True):
1674
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1676
def fileids_altered_by_revision_ids(self, revision_ids):
1678
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1680
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1682
return self._real_repository._get_versioned_file_checker(
1683
revisions, revision_versions_cache)
1685
def iter_files_bytes(self, desired_files):
1686
"""See Repository.iter_file_bytes.
1689
return self._real_repository.iter_files_bytes(desired_files)
1691
def get_cached_parent_map(self, revision_ids):
1692
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
1693
return self._unstacked_provider.get_cached_parent_map(revision_ids)
1695
def get_parent_map(self, revision_ids):
1696
"""See bzrlib.Graph.get_parent_map()."""
1697
return self._make_parents_provider().get_parent_map(revision_ids)
1699
def _get_parent_map_rpc(self, keys):
1700
"""Helper for get_parent_map that performs the RPC."""
1701
medium = self._client._medium
1702
if medium._is_remote_before((1, 2)):
1703
# We already found out that the server can't understand
1704
# Repository.get_parent_map requests, so just fetch the whole
1707
# Note that this reads the whole graph, when only some keys are
1708
# wanted. On this old server there's no way (?) to get them all
1709
# in one go, and the user probably will have seen a warning about
1710
# the server being old anyhow.
1711
rg = self._get_revision_graph(None)
1712
# There is an API discrepancy between get_parent_map and
1713
# get_revision_graph. Specifically, a "key:()" pair in
1714
# get_revision_graph just means a node has no parents. For
1715
# "get_parent_map" it means the node is a ghost. So fix up the
1716
# graph to correct this.
1717
# https://bugs.launchpad.net/bzr/+bug/214894
1718
# There is one other "bug" which is that ghosts in
1719
# get_revision_graph() are not returned at all. But we won't worry
1720
# about that for now.
1721
for node_id, parent_ids in rg.iteritems():
1722
if parent_ids == ():
1723
rg[node_id] = (NULL_REVISION,)
1724
rg[NULL_REVISION] = ()
1729
raise ValueError('get_parent_map(None) is not valid')
1730
if NULL_REVISION in keys:
1731
keys.discard(NULL_REVISION)
1732
found_parents = {NULL_REVISION:()}
1734
return found_parents
1737
# TODO(Needs analysis): We could assume that the keys being requested
1738
# from get_parent_map are in a breadth first search, so typically they
1739
# will all be depth N from some common parent, and we don't have to
1740
# have the server iterate from the root parent, but rather from the
1741
# keys we're searching; and just tell the server the keyspace we
1742
# already have; but this may be more traffic again.
1744
# Transform self._parents_map into a search request recipe.
1745
# TODO: Manage this incrementally to avoid covering the same path
1746
# repeatedly. (The server will have to on each request, but the less
1747
# work done the better).
1749
# Negative caching notes:
1750
# new server sends missing when a request including the revid
1751
# 'include-missing:' is present in the request.
1752
# missing keys are serialised as missing:X, and we then call
1753
# provider.note_missing(X) for-all X
1754
parents_map = self._unstacked_provider.get_cached_map()
1755
if parents_map is None:
1756
# Repository is not locked, so there's no cache.
1758
if _DEFAULT_SEARCH_DEPTH <= 0:
1759
(start_set, stop_keys,
1760
key_count) = graph.search_result_from_parent_map(
1761
parents_map, self._unstacked_provider.missing_keys)
1763
(start_set, stop_keys,
1764
key_count) = graph.limited_search_result_from_parent_map(
1765
parents_map, self._unstacked_provider.missing_keys,
1766
keys, depth=_DEFAULT_SEARCH_DEPTH)
1767
recipe = ('manual', start_set, stop_keys, key_count)
1768
body = self._serialise_search_recipe(recipe)
1769
path = self.bzrdir._path_for_remote_call(self._client)
1771
if type(key) is not str:
1773
"key %r not a plain string" % (key,))
1774
verb = 'Repository.get_parent_map'
1775
args = (path, 'include-missing:') + tuple(keys)
1777
response = self._call_with_body_bytes_expecting_body(
1779
except errors.UnknownSmartMethod:
1780
# Server does not support this method, so get the whole graph.
1781
# Worse, we have to force a disconnection, because the server now
1782
# doesn't realise it has a body on the wire to consume, so the
1783
# only way to recover is to abandon the connection.
1785
'Server is too old for fast get_parent_map, reconnecting. '
1786
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1788
# To avoid having to disconnect repeatedly, we keep track of the
1789
# fact the server doesn't understand remote methods added in 1.2.
1790
medium._remember_remote_is_before((1, 2))
1791
# Recurse just once and we should use the fallback code.
1792
return self._get_parent_map_rpc(keys)
1793
response_tuple, response_handler = response
1794
if response_tuple[0] not in ['ok']:
1795
response_handler.cancel_read_body()
1796
raise errors.UnexpectedSmartServerResponse(response_tuple)
1797
if response_tuple[0] == 'ok':
1798
coded = bz2.decompress(response_handler.read_body_bytes())
1800
# no revisions found
1802
lines = coded.split('\n')
1805
d = tuple(line.split())
1807
revision_graph[d[0]] = d[1:]
1810
if d[0].startswith('missing:'):
1812
self._unstacked_provider.note_missing_key(revid)
1814
# no parents - so give the Graph result
1816
revision_graph[d[0]] = (NULL_REVISION,)
1817
return revision_graph
1820
def get_signature_text(self, revision_id):
1822
return self._real_repository.get_signature_text(revision_id)
1825
def _get_inventory_xml(self, revision_id):
1827
return self._real_repository._get_inventory_xml(revision_id)
1829
def reconcile(self, other=None, thorough=False):
1831
return self._real_repository.reconcile(other=other, thorough=thorough)
1833
def all_revision_ids(self):
1835
return self._real_repository.all_revision_ids()
1838
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1840
return self._real_repository.get_deltas_for_revisions(revisions,
1841
specific_fileids=specific_fileids)
1844
def get_revision_delta(self, revision_id, specific_fileids=None):
1846
return self._real_repository.get_revision_delta(revision_id,
1847
specific_fileids=specific_fileids)
1850
def revision_trees(self, revision_ids):
1852
return self._real_repository.revision_trees(revision_ids)
1855
def get_revision_reconcile(self, revision_id):
1857
return self._real_repository.get_revision_reconcile(revision_id)
1860
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1862
return self._real_repository.check(revision_ids=revision_ids,
1863
callback_refs=callback_refs, check_repo=check_repo)
1865
def copy_content_into(self, destination, revision_id=None):
1867
return self._real_repository.copy_content_into(
1868
destination, revision_id=revision_id)
1870
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1871
# get a tarball of the remote repository, and copy from that into the
1873
from bzrlib import osutils
1875
# TODO: Maybe a progress bar while streaming the tarball?
1876
note("Copying repository content as tarball...")
1877
tar_file = self._get_tarball('bz2')
1878
if tar_file is None:
1880
destination = to_bzrdir.create_repository()
1882
tar = tarfile.open('repository', fileobj=tar_file,
1884
tmpdir = osutils.mkdtemp()
1886
_extract_tar(tar, tmpdir)
1887
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1888
tmp_repo = tmp_bzrdir.open_repository()
1889
tmp_repo.copy_content_into(destination, revision_id)
1891
osutils.rmtree(tmpdir)
1895
# TODO: Suggestion from john: using external tar is much faster than
1896
# python's tarfile library, but it may not work on windows.
1899
def inventories(self):
1900
"""Decorate the real repository for now.
1902
In the long term a full blown network facility is needed to
1903
avoid creating a real repository object locally.
1906
return self._real_repository.inventories
1909
def pack(self, hint=None, clean_obsolete_packs=False):
1910
"""Compress the data within the repository.
1912
This is not currently implemented within the smart server.
1915
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1918
def revisions(self):
1919
"""Decorate the real repository for now.
1921
In the short term this should become a real object to intercept graph
1924
In the long term a full blown network facility is needed.
1927
return self._real_repository.revisions
1929
def set_make_working_trees(self, new_value):
1931
new_value_str = "True"
1933
new_value_str = "False"
1934
path = self.bzrdir._path_for_remote_call(self._client)
1936
response = self._call(
1937
'Repository.set_make_working_trees', path, new_value_str)
1938
except errors.UnknownSmartMethod:
1940
self._real_repository.set_make_working_trees(new_value)
1942
if response[0] != 'ok':
1943
raise errors.UnexpectedSmartServerResponse(response)
1946
def signatures(self):
1947
"""Decorate the real repository for now.
1949
In the long term a full blown network facility is needed to avoid
1950
creating a real repository object locally.
1953
return self._real_repository.signatures
1956
def sign_revision(self, revision_id, gpg_strategy):
1958
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1962
"""Decorate the real repository for now.
1964
In the long term a full blown network facility is needed to avoid
1965
creating a real repository object locally.
1968
return self._real_repository.texts
1971
def get_revisions(self, revision_ids):
1973
return self._real_repository.get_revisions(revision_ids)
1975
def supports_rich_root(self):
1976
return self._format.rich_root_data
1978
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1979
def iter_reverse_revision_history(self, revision_id):
1981
return self._real_repository.iter_reverse_revision_history(revision_id)
1984
def _serializer(self):
1985
return self._format._serializer
1987
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1989
return self._real_repository.store_revision_signature(
1990
gpg_strategy, plaintext, revision_id)
1992
def add_signature_text(self, revision_id, signature):
1994
return self._real_repository.add_signature_text(revision_id, signature)
1996
def has_signature_for_revision_id(self, revision_id):
1998
return self._real_repository.has_signature_for_revision_id(revision_id)
2000
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2002
return self._real_repository.item_keys_introduced_by(revision_ids,
2003
_files_pb=_files_pb)
2005
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2007
return self._real_repository._find_inconsistent_revision_parents(
2010
def _check_for_inconsistent_revision_parents(self):
2012
return self._real_repository._check_for_inconsistent_revision_parents()
2014
def _make_parents_provider(self, other=None):
2015
providers = [self._unstacked_provider]
2016
if other is not None:
2017
providers.insert(0, other)
2018
return graph.StackedParentsProvider(_LazyListJoin(
2019
providers, self._fallback_repositories))
2021
def _serialise_search_recipe(self, recipe):
2022
"""Serialise a graph search recipe.
2024
:param recipe: A search recipe (start, stop, count).
2025
:return: Serialised bytes.
2027
start_keys = ' '.join(recipe[1])
2028
stop_keys = ' '.join(recipe[2])
2029
count = str(recipe[3])
2030
return '\n'.join((start_keys, stop_keys, count))
2032
def _serialise_search_result(self, search_result):
2033
parts = search_result.get_network_struct()
2034
return '\n'.join(parts)
2037
path = self.bzrdir._path_for_remote_call(self._client)
2039
response = self._call('PackRepository.autopack', path)
2040
except errors.UnknownSmartMethod:
2042
self._real_repository._pack_collection.autopack()
2045
if response[0] != 'ok':
2046
raise errors.UnexpectedSmartServerResponse(response)
2049
class RemoteStreamSink(vf_repository.StreamSink):
2051
def _insert_real(self, stream, src_format, resume_tokens):
2052
self.target_repo._ensure_real()
2053
sink = self.target_repo._real_repository._get_sink()
2054
result = sink.insert_stream(stream, src_format, resume_tokens)
2056
self.target_repo.autopack()
2059
def insert_stream(self, stream, src_format, resume_tokens):
2060
target = self.target_repo
2061
target._unstacked_provider.missing_keys.clear()
2062
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2063
if target._lock_token:
2064
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2065
lock_args = (target._lock_token or '',)
2067
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2069
client = target._client
2070
medium = client._medium
2071
path = target.bzrdir._path_for_remote_call(client)
2072
# Probe for the verb to use with an empty stream before sending the
2073
# real stream to it. We do this both to avoid the risk of sending a
2074
# large request that is then rejected, and because we don't want to
2075
# implement a way to buffer, rewind, or restart the stream.
2077
for verb, required_version in candidate_calls:
2078
if medium._is_remote_before(required_version):
2081
# We've already done the probing (and set _is_remote_before) on
2082
# a previous insert.
2085
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2087
response = client.call_with_body_stream(
2088
(verb, path, '') + lock_args, byte_stream)
2089
except errors.UnknownSmartMethod:
2090
medium._remember_remote_is_before(required_version)
2096
return self._insert_real(stream, src_format, resume_tokens)
2097
self._last_inv_record = None
2098
self._last_substream = None
2099
if required_version < (1, 19):
2100
# Remote side doesn't support inventory deltas. Wrap the stream to
2101
# make sure we don't send any. If the stream contains inventory
2102
# deltas we'll interrupt the smart insert_stream request and
2104
stream = self._stop_stream_if_inventory_delta(stream)
2105
byte_stream = smart_repo._stream_to_byte_stream(
2107
resume_tokens = ' '.join(resume_tokens)
2108
response = client.call_with_body_stream(
2109
(verb, path, resume_tokens) + lock_args, byte_stream)
2110
if response[0][0] not in ('ok', 'missing-basis'):
2111
raise errors.UnexpectedSmartServerResponse(response)
2112
if self._last_substream is not None:
2113
# The stream included an inventory-delta record, but the remote
2114
# side isn't new enough to support them. So we need to send the
2115
# rest of the stream via VFS.
2116
self.target_repo.refresh_data()
2117
return self._resume_stream_with_vfs(response, src_format)
2118
if response[0][0] == 'missing-basis':
2119
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2120
resume_tokens = tokens
2121
return resume_tokens, set(missing_keys)
2123
self.target_repo.refresh_data()
2126
def _resume_stream_with_vfs(self, response, src_format):
2127
"""Resume sending a stream via VFS, first resending the record and
2128
substream that couldn't be sent via an insert_stream verb.
2130
if response[0][0] == 'missing-basis':
2131
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2132
# Ignore missing_keys, we haven't finished inserting yet
2135
def resume_substream():
2136
# Yield the substream that was interrupted.
2137
for record in self._last_substream:
2139
self._last_substream = None
2140
def resume_stream():
2141
# Finish sending the interrupted substream
2142
yield ('inventory-deltas', resume_substream())
2143
# Then simply continue sending the rest of the stream.
2144
for substream_kind, substream in self._last_stream:
2145
yield substream_kind, substream
2146
return self._insert_real(resume_stream(), src_format, tokens)
2148
def _stop_stream_if_inventory_delta(self, stream):
2149
"""Normally this just lets the original stream pass-through unchanged.
2151
However if any 'inventory-deltas' substream occurs it will stop
2152
streaming, and store the interrupted substream and stream in
2153
self._last_substream and self._last_stream so that the stream can be
2154
resumed by _resume_stream_with_vfs.
2157
stream_iter = iter(stream)
2158
for substream_kind, substream in stream_iter:
2159
if substream_kind == 'inventory-deltas':
2160
self._last_substream = substream
2161
self._last_stream = stream_iter
2164
yield substream_kind, substream
2167
class RemoteStreamSource(vf_repository.StreamSource):
2168
"""Stream data from a remote server."""
2170
def get_stream(self, search):
2171
if (self.from_repository._fallback_repositories and
2172
self.to_format._fetch_order == 'topological'):
2173
return self._real_stream(self.from_repository, search)
2176
repos = [self.from_repository]
2182
repos.extend(repo._fallback_repositories)
2183
sources.append(repo)
2184
return self.missing_parents_chain(search, sources)
2186
def get_stream_for_missing_keys(self, missing_keys):
2187
self.from_repository._ensure_real()
2188
real_repo = self.from_repository._real_repository
2189
real_source = real_repo._get_source(self.to_format)
2190
return real_source.get_stream_for_missing_keys(missing_keys)
2192
def _real_stream(self, repo, search):
2193
"""Get a stream for search from repo.
2195
This never called RemoteStreamSource.get_stream, and is a heler
2196
for RemoteStreamSource._get_stream to allow getting a stream
2197
reliably whether fallback back because of old servers or trying
2198
to stream from a non-RemoteRepository (which the stacked support
2201
source = repo._get_source(self.to_format)
2202
if isinstance(source, RemoteStreamSource):
2204
source = repo._real_repository._get_source(self.to_format)
2205
return source.get_stream(search)
2207
def _get_stream(self, repo, search):
2208
"""Core worker to get a stream from repo for search.
2210
This is used by both get_stream and the stacking support logic. It
2211
deliberately gets a stream for repo which does not need to be
2212
self.from_repository. In the event that repo is not Remote, or
2213
cannot do a smart stream, a fallback is made to the generic
2214
repository._get_stream() interface, via self._real_stream.
2216
In the event of stacking, streams from _get_stream will not
2217
contain all the data for search - this is normal (see get_stream).
2219
:param repo: A repository.
2220
:param search: A search.
2222
# Fallbacks may be non-smart
2223
if not isinstance(repo, RemoteRepository):
2224
return self._real_stream(repo, search)
2225
client = repo._client
2226
medium = client._medium
2227
path = repo.bzrdir._path_for_remote_call(client)
2228
search_bytes = repo._serialise_search_result(search)
2229
args = (path, self.to_format.network_name())
2231
('Repository.get_stream_1.19', (1, 19)),
2232
('Repository.get_stream', (1, 13))]
2235
for verb, version in candidate_verbs:
2236
if medium._is_remote_before(version):
2239
response = repo._call_with_body_bytes_expecting_body(
2240
verb, args, search_bytes)
2241
except errors.UnknownSmartMethod:
2242
medium._remember_remote_is_before(version)
2243
except errors.UnknownErrorFromSmartServer, e:
2244
if isinstance(search, graph.EverythingResult):
2245
error_verb = e.error_from_smart_server.error_verb
2246
if error_verb == 'BadSearch':
2247
# Pre-2.4 servers don't support this sort of search.
2248
# XXX: perhaps falling back to VFS on BadSearch is a
2249
# good idea in general? It might provide a little bit
2250
# of protection against client-side bugs.
2251
medium._remember_remote_is_before((2, 4))
2255
response_tuple, response_handler = response
2259
return self._real_stream(repo, search)
2260
if response_tuple[0] != 'ok':
2261
raise errors.UnexpectedSmartServerResponse(response_tuple)
2262
byte_stream = response_handler.read_streamed_body()
2263
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2264
self._record_counter)
2265
if src_format.network_name() != repo._format.network_name():
2266
raise AssertionError(
2267
"Mismatched RemoteRepository and stream src %r, %r" % (
2268
src_format.network_name(), repo._format.network_name()))
2271
def missing_parents_chain(self, search, sources):
2272
"""Chain multiple streams together to handle stacking.
2274
:param search: The overall search to satisfy with streams.
2275
:param sources: A list of Repository objects to query.
2277
self.from_serialiser = self.from_repository._format._serializer
2278
self.seen_revs = set()
2279
self.referenced_revs = set()
2280
# If there are heads in the search, or the key count is > 0, we are not
2282
while not search.is_empty() and len(sources) > 1:
2283
source = sources.pop(0)
2284
stream = self._get_stream(source, search)
2285
for kind, substream in stream:
2286
if kind != 'revisions':
2287
yield kind, substream
2289
yield kind, self.missing_parents_rev_handler(substream)
2290
search = search.refine(self.seen_revs, self.referenced_revs)
2291
self.seen_revs = set()
2292
self.referenced_revs = set()
2293
if not search.is_empty():
2294
for kind, stream in self._get_stream(sources[0], search):
2297
def missing_parents_rev_handler(self, substream):
2298
for content in substream:
2299
revision_bytes = content.get_bytes_as('fulltext')
2300
revision = self.from_serialiser.read_revision_from_string(
2302
self.seen_revs.add(content.key[-1])
2303
self.referenced_revs.update(revision.parent_ids)
2307
class RemoteBranchLockableFiles(LockableFiles):
2308
"""A 'LockableFiles' implementation that talks to a smart server.
2310
This is not a public interface class.
2313
def __init__(self, bzrdir, _client):
2314
self.bzrdir = bzrdir
2315
self._client = _client
2316
self._need_find_modes = True
2317
LockableFiles.__init__(
2318
self, bzrdir.get_branch_transport(None),
2319
'lock', lockdir.LockDir)
2321
def _find_modes(self):
2322
# RemoteBranches don't let the client set the mode of control files.
2323
self._dir_mode = None
2324
self._file_mode = None
2327
class RemoteBranchFormat(branch.BranchFormat):
2329
def __init__(self, network_name=None):
2330
super(RemoteBranchFormat, self).__init__()
2331
self._matchingbzrdir = RemoteBzrDirFormat()
2332
self._matchingbzrdir.set_branch_format(self)
2333
self._custom_format = None
2334
self._network_name = network_name
2336
def __eq__(self, other):
2337
return (isinstance(other, RemoteBranchFormat) and
2338
self.__dict__ == other.__dict__)
2340
def _ensure_real(self):
2341
if self._custom_format is None:
2342
self._custom_format = branch.network_format_registry.get(
2345
def get_format_description(self):
2347
return 'Remote: ' + self._custom_format.get_format_description()
2349
def network_name(self):
2350
return self._network_name
2352
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2353
return a_bzrdir.open_branch(name=name,
2354
ignore_fallbacks=ignore_fallbacks)
2356
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
2357
# Initialisation when using a local bzrdir object, or a non-vfs init
2358
# method is not available on the server.
2359
# self._custom_format is always set - the start of initialize ensures
2361
if isinstance(a_bzrdir, RemoteBzrDir):
2362
a_bzrdir._ensure_real()
2363
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2364
name, append_revisions_only=append_revisions_only)
2366
# We assume the bzrdir is parameterised; it may not be.
2367
result = self._custom_format.initialize(a_bzrdir, name,
2368
append_revisions_only=append_revisions_only)
2369
if (isinstance(a_bzrdir, RemoteBzrDir) and
2370
not isinstance(result, RemoteBranch)):
2371
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2375
def initialize(self, a_bzrdir, name=None, repository=None,
2376
append_revisions_only=None):
2377
# 1) get the network name to use.
2378
if self._custom_format:
2379
network_name = self._custom_format.network_name()
2381
# Select the current bzrlib default and ask for that.
2382
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2383
reference_format = reference_bzrdir_format.get_branch_format()
2384
self._custom_format = reference_format
2385
network_name = reference_format.network_name()
2386
# Being asked to create on a non RemoteBzrDir:
2387
if not isinstance(a_bzrdir, RemoteBzrDir):
2388
return self._vfs_initialize(a_bzrdir, name=name,
2389
append_revisions_only=append_revisions_only)
2390
medium = a_bzrdir._client._medium
2391
if medium._is_remote_before((1, 13)):
2392
return self._vfs_initialize(a_bzrdir, name=name,
2393
append_revisions_only=append_revisions_only)
2394
# Creating on a remote bzr dir.
2395
# 2) try direct creation via RPC
2396
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2397
if name is not None:
2398
# XXX JRV20100304: Support creating colocated branches
2399
raise errors.NoColocatedBranchSupport(self)
2400
verb = 'BzrDir.create_branch'
2402
response = a_bzrdir._call(verb, path, network_name)
2403
except errors.UnknownSmartMethod:
2404
# Fallback - use vfs methods
2405
medium._remember_remote_is_before((1, 13))
2406
return self._vfs_initialize(a_bzrdir, name=name,
2407
append_revisions_only=append_revisions_only)
2408
if response[0] != 'ok':
2409
raise errors.UnexpectedSmartServerResponse(response)
2410
# Turn the response into a RemoteRepository object.
2411
format = RemoteBranchFormat(network_name=response[1])
2412
repo_format = response_tuple_to_repo_format(response[3:])
2413
repo_path = response[2]
2414
if repository is not None:
2415
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2416
url_diff = urlutils.relative_url(repository.user_url,
2419
raise AssertionError(
2420
'repository.user_url %r does not match URL from server '
2421
'response (%r + %r)'
2422
% (repository.user_url, a_bzrdir.user_url, repo_path))
2423
remote_repo = repository
2426
repo_bzrdir = a_bzrdir
2428
repo_bzrdir = RemoteBzrDir(
2429
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2431
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2432
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2433
format=format, setup_stacking=False, name=name)
2434
if append_revisions_only:
2435
remote_branch.set_append_revisions_only(append_revisions_only)
2436
# XXX: We know this is a new branch, so it must have revno 0, revid
2437
# NULL_REVISION. Creating the branch locked would make this be unable
2438
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2439
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2440
return remote_branch
2442
def make_tags(self, branch):
2444
return self._custom_format.make_tags(branch)
2446
def supports_tags(self):
2447
# Remote branches might support tags, but we won't know until we
2448
# access the real remote branch.
2450
return self._custom_format.supports_tags()
2452
def supports_stacking(self):
2454
return self._custom_format.supports_stacking()
2456
def supports_set_append_revisions_only(self):
2458
return self._custom_format.supports_set_append_revisions_only()
2460
def _use_default_local_heads_to_fetch(self):
2461
# If the branch format is a metadir format *and* its heads_to_fetch
2462
# implementation is not overridden vs the base class, we can use the
2463
# base class logic rather than use the heads_to_fetch RPC. This is
2464
# usually cheaper in terms of net round trips, as the last-revision and
2465
# tags info fetched is cached and would be fetched anyway.
2467
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2468
branch_class = self._custom_format._branch_class()
2469
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2470
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2474
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2475
"""Branch stored on a server accessed by HPSS RPC.
2477
At the moment most operations are mapped down to simple file operations.
2480
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2481
_client=None, format=None, setup_stacking=True, name=None):
2482
"""Create a RemoteBranch instance.
2484
:param real_branch: An optional local implementation of the branch
2485
format, usually accessing the data via the VFS.
2486
:param _client: Private parameter for testing.
2487
:param format: A RemoteBranchFormat object, None to create one
2488
automatically. If supplied it should have a network_name already
2490
:param setup_stacking: If True make an RPC call to determine the
2491
stacked (or not) status of the branch. If False assume the branch
2493
:param name: Colocated branch name
2495
# We intentionally don't call the parent class's __init__, because it
2496
# will try to assign to self.tags, which is a property in this subclass.
2497
# And the parent's __init__ doesn't do much anyway.
2498
self.bzrdir = remote_bzrdir
2499
if _client is not None:
2500
self._client = _client
2502
self._client = remote_bzrdir._client
2503
self.repository = remote_repository
2504
if real_branch is not None:
2505
self._real_branch = real_branch
2506
# Give the remote repository the matching real repo.
2507
real_repo = self._real_branch.repository
2508
if isinstance(real_repo, RemoteRepository):
2509
real_repo._ensure_real()
2510
real_repo = real_repo._real_repository
2511
self.repository._set_real_repository(real_repo)
2512
# Give the branch the remote repository to let fast-pathing happen.
2513
self._real_branch.repository = self.repository
2515
self._real_branch = None
2516
# Fill out expected attributes of branch for bzrlib API users.
2517
self._clear_cached_state()
2518
# TODO: deprecate self.base in favor of user_url
2519
self.base = self.bzrdir.user_url
2521
self._control_files = None
2522
self._lock_mode = None
2523
self._lock_token = None
2524
self._repo_lock_token = None
2525
self._lock_count = 0
2526
self._leave_lock = False
2527
# Setup a format: note that we cannot call _ensure_real until all the
2528
# attributes above are set: This code cannot be moved higher up in this
2531
self._format = RemoteBranchFormat()
2532
if real_branch is not None:
2533
self._format._network_name = \
2534
self._real_branch._format.network_name()
2536
self._format = format
2537
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2538
# branch.open_branch method.
2539
self._real_ignore_fallbacks = not setup_stacking
2540
if not self._format._network_name:
2541
# Did not get from open_branchV2 - old server.
2543
self._format._network_name = \
2544
self._real_branch._format.network_name()
2545
self.tags = self._format.make_tags(self)
2546
# The base class init is not called, so we duplicate this:
2547
hooks = branch.Branch.hooks['open']
2550
self._is_stacked = False
2552
self._setup_stacking()
2554
def _setup_stacking(self):
2555
# configure stacking into the remote repository, by reading it from
2558
fallback_url = self.get_stacked_on_url()
2559
except (errors.NotStacked, errors.UnstackableBranchFormat,
2560
errors.UnstackableRepositoryFormat), e:
2562
self._is_stacked = True
2563
self._activate_fallback_location(fallback_url)
2565
def _get_config(self):
2566
return RemoteBranchConfig(self)
2568
def _get_real_transport(self):
2569
# if we try vfs access, return the real branch's vfs transport
2571
return self._real_branch._transport
2573
_transport = property(_get_real_transport)
2576
return "%s(%s)" % (self.__class__.__name__, self.base)
2580
def _ensure_real(self):
2581
"""Ensure that there is a _real_branch set.
2583
Used before calls to self._real_branch.
2585
if self._real_branch is None:
2586
if not vfs.vfs_enabled():
2587
raise AssertionError('smart server vfs must be enabled '
2588
'to use vfs implementation')
2589
self.bzrdir._ensure_real()
2590
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2591
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2592
if self.repository._real_repository is None:
2593
# Give the remote repository the matching real repo.
2594
real_repo = self._real_branch.repository
2595
if isinstance(real_repo, RemoteRepository):
2596
real_repo._ensure_real()
2597
real_repo = real_repo._real_repository
2598
self.repository._set_real_repository(real_repo)
2599
# Give the real branch the remote repository to let fast-pathing
2601
self._real_branch.repository = self.repository
2602
if self._lock_mode == 'r':
2603
self._real_branch.lock_read()
2604
elif self._lock_mode == 'w':
2605
self._real_branch.lock_write(token=self._lock_token)
2607
def _translate_error(self, err, **context):
2608
self.repository._translate_error(err, branch=self, **context)
2610
def _clear_cached_state(self):
2611
super(RemoteBranch, self)._clear_cached_state()
2612
if self._real_branch is not None:
2613
self._real_branch._clear_cached_state()
2615
def _clear_cached_state_of_remote_branch_only(self):
2616
"""Like _clear_cached_state, but doesn't clear the cache of
2619
This is useful when falling back to calling a method of
2620
self._real_branch that changes state. In that case the underlying
2621
branch changes, so we need to invalidate this RemoteBranch's cache of
2622
it. However, there's no need to invalidate the _real_branch's cache
2623
too, in fact doing so might harm performance.
2625
super(RemoteBranch, self)._clear_cached_state()
2628
def control_files(self):
2629
# Defer actually creating RemoteBranchLockableFiles until its needed,
2630
# because it triggers an _ensure_real that we otherwise might not need.
2631
if self._control_files is None:
2632
self._control_files = RemoteBranchLockableFiles(
2633
self.bzrdir, self._client)
2634
return self._control_files
2636
def _get_checkout_format(self, lightweight=False):
2639
format = RemoteBzrDirFormat()
2640
self.bzrdir._format._supply_sub_formats_to(format)
2641
format.workingtree_format = self._real_branch._get_checkout_format(
2642
lightweight=lightweight).workingtree_format
2645
return self._real_branch._get_checkout_format(lightweight=False)
2647
def get_physical_lock_status(self):
2648
"""See Branch.get_physical_lock_status()."""
2649
# should be an API call to the server, as branches must be lockable.
2651
return self._real_branch.get_physical_lock_status()
2653
def get_stacked_on_url(self):
2654
"""Get the URL this branch is stacked against.
2656
:raises NotStacked: If the branch is not stacked.
2657
:raises UnstackableBranchFormat: If the branch does not support
2659
:raises UnstackableRepositoryFormat: If the repository does not support
2663
# there may not be a repository yet, so we can't use
2664
# self._translate_error, so we can't use self._call either.
2665
response = self._client.call('Branch.get_stacked_on_url',
2666
self._remote_path())
2667
except errors.ErrorFromSmartServer, err:
2668
# there may not be a repository yet, so we can't call through
2669
# its _translate_error
2670
_translate_error(err, branch=self)
2671
except errors.UnknownSmartMethod, err:
2673
return self._real_branch.get_stacked_on_url()
2674
if response[0] != 'ok':
2675
raise errors.UnexpectedSmartServerResponse(response)
2678
def set_stacked_on_url(self, url):
2679
branch.Branch.set_stacked_on_url(self, url)
2681
self._is_stacked = False
2683
self._is_stacked = True
2685
def _vfs_get_tags_bytes(self):
2687
return self._real_branch._get_tags_bytes()
2690
def _get_tags_bytes(self):
2691
if self._tags_bytes is None:
2692
self._tags_bytes = self._get_tags_bytes_via_hpss()
2693
return self._tags_bytes
2695
def _get_tags_bytes_via_hpss(self):
2696
medium = self._client._medium
2697
if medium._is_remote_before((1, 13)):
2698
return self._vfs_get_tags_bytes()
2700
response = self._call('Branch.get_tags_bytes', self._remote_path())
2701
except errors.UnknownSmartMethod:
2702
medium._remember_remote_is_before((1, 13))
2703
return self._vfs_get_tags_bytes()
2706
def _vfs_set_tags_bytes(self, bytes):
2708
return self._real_branch._set_tags_bytes(bytes)
2710
def _set_tags_bytes(self, bytes):
2711
if self.is_locked():
2712
self._tags_bytes = bytes
2713
medium = self._client._medium
2714
if medium._is_remote_before((1, 18)):
2715
self._vfs_set_tags_bytes(bytes)
2719
self._remote_path(), self._lock_token, self._repo_lock_token)
2720
response = self._call_with_body_bytes(
2721
'Branch.set_tags_bytes', args, bytes)
2722
except errors.UnknownSmartMethod:
2723
medium._remember_remote_is_before((1, 18))
2724
self._vfs_set_tags_bytes(bytes)
2726
def lock_read(self):
2727
"""Lock the branch for read operations.
2729
:return: A bzrlib.lock.LogicalLockResult.
2731
self.repository.lock_read()
2732
if not self._lock_mode:
2733
self._note_lock('r')
2734
self._lock_mode = 'r'
2735
self._lock_count = 1
2736
if self._real_branch is not None:
2737
self._real_branch.lock_read()
2739
self._lock_count += 1
2740
return lock.LogicalLockResult(self.unlock)
2742
def _remote_lock_write(self, token):
2744
branch_token = repo_token = ''
2746
branch_token = token
2747
repo_token = self.repository.lock_write().repository_token
2748
self.repository.unlock()
2749
err_context = {'token': token}
2751
response = self._call(
2752
'Branch.lock_write', self._remote_path(), branch_token,
2753
repo_token or '', **err_context)
2754
except errors.LockContention, e:
2755
# The LockContention from the server doesn't have any
2756
# information about the lock_url. We re-raise LockContention
2757
# with valid lock_url.
2758
raise errors.LockContention('(remote lock)',
2759
self.repository.base.split('.bzr/')[0])
2760
if response[0] != 'ok':
2761
raise errors.UnexpectedSmartServerResponse(response)
2762
ok, branch_token, repo_token = response
2763
return branch_token, repo_token
2765
def lock_write(self, token=None):
2766
if not self._lock_mode:
2767
self._note_lock('w')
2768
# Lock the branch and repo in one remote call.
2769
remote_tokens = self._remote_lock_write(token)
2770
self._lock_token, self._repo_lock_token = remote_tokens
2771
if not self._lock_token:
2772
raise SmartProtocolError('Remote server did not return a token!')
2773
# Tell the self.repository object that it is locked.
2774
self.repository.lock_write(
2775
self._repo_lock_token, _skip_rpc=True)
2777
if self._real_branch is not None:
2778
self._real_branch.lock_write(token=self._lock_token)
2779
if token is not None:
2780
self._leave_lock = True
2782
self._leave_lock = False
2783
self._lock_mode = 'w'
2784
self._lock_count = 1
2785
elif self._lock_mode == 'r':
2786
raise errors.ReadOnlyError(self)
2788
if token is not None:
2789
# A token was given to lock_write, and we're relocking, so
2790
# check that the given token actually matches the one we
2792
if token != self._lock_token:
2793
raise errors.TokenMismatch(token, self._lock_token)
2794
self._lock_count += 1
2795
# Re-lock the repository too.
2796
self.repository.lock_write(self._repo_lock_token)
2797
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2799
def _unlock(self, branch_token, repo_token):
2800
err_context = {'token': str((branch_token, repo_token))}
2801
response = self._call(
2802
'Branch.unlock', self._remote_path(), branch_token,
2803
repo_token or '', **err_context)
2804
if response == ('ok',):
2806
raise errors.UnexpectedSmartServerResponse(response)
2808
@only_raises(errors.LockNotHeld, errors.LockBroken)
2811
self._lock_count -= 1
2812
if not self._lock_count:
2813
self._clear_cached_state()
2814
mode = self._lock_mode
2815
self._lock_mode = None
2816
if self._real_branch is not None:
2817
if (not self._leave_lock and mode == 'w' and
2818
self._repo_lock_token):
2819
# If this RemoteBranch will remove the physical lock
2820
# for the repository, make sure the _real_branch
2821
# doesn't do it first. (Because the _real_branch's
2822
# repository is set to be the RemoteRepository.)
2823
self._real_branch.repository.leave_lock_in_place()
2824
self._real_branch.unlock()
2826
# Only write-locked branched need to make a remote method
2827
# call to perform the unlock.
2829
if not self._lock_token:
2830
raise AssertionError('Locked, but no token!')
2831
branch_token = self._lock_token
2832
repo_token = self._repo_lock_token
2833
self._lock_token = None
2834
self._repo_lock_token = None
2835
if not self._leave_lock:
2836
self._unlock(branch_token, repo_token)
2838
self.repository.unlock()
2840
def break_lock(self):
2842
return self._real_branch.break_lock()
2844
def leave_lock_in_place(self):
2845
if not self._lock_token:
2846
raise NotImplementedError(self.leave_lock_in_place)
2847
self._leave_lock = True
2849
def dont_leave_lock_in_place(self):
2850
if not self._lock_token:
2851
raise NotImplementedError(self.dont_leave_lock_in_place)
2852
self._leave_lock = False
2855
def get_rev_id(self, revno, history=None):
2857
return _mod_revision.NULL_REVISION
2858
last_revision_info = self.last_revision_info()
2859
ok, result = self.repository.get_rev_id_for_revno(
2860
revno, last_revision_info)
2863
missing_parent = result[1]
2864
# Either the revision named by the server is missing, or its parent
2865
# is. Call get_parent_map to determine which, so that we report a
2867
parent_map = self.repository.get_parent_map([missing_parent])
2868
if missing_parent in parent_map:
2869
missing_parent = parent_map[missing_parent]
2870
raise errors.RevisionNotPresent(missing_parent, self.repository)
2872
def _read_last_revision_info(self):
2873
response = self._call('Branch.last_revision_info', self._remote_path())
2874
if response[0] != 'ok':
2875
raise SmartProtocolError('unexpected response code %s' % (response,))
2876
revno = int(response[1])
2877
last_revision = response[2]
2878
return (revno, last_revision)
2880
def _gen_revision_history(self):
2881
"""See Branch._gen_revision_history()."""
2882
if self._is_stacked:
2884
return self._real_branch._gen_revision_history()
2885
response_tuple, response_handler = self._call_expecting_body(
2886
'Branch.revision_history', self._remote_path())
2887
if response_tuple[0] != 'ok':
2888
raise errors.UnexpectedSmartServerResponse(response_tuple)
2889
result = response_handler.read_body_bytes().split('\x00')
2894
def _remote_path(self):
2895
return self.bzrdir._path_for_remote_call(self._client)
2897
def _set_last_revision_descendant(self, revision_id, other_branch,
2898
allow_diverged=False, allow_overwrite_descendant=False):
2899
# This performs additional work to meet the hook contract; while its
2900
# undesirable, we have to synthesise the revno to call the hook, and
2901
# not calling the hook is worse as it means changes can't be prevented.
2902
# Having calculated this though, we can't just call into
2903
# set_last_revision_info as a simple call, because there is a set_rh
2904
# hook that some folk may still be using.
2905
old_revno, old_revid = self.last_revision_info()
2906
history = self._lefthand_history(revision_id)
2907
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2908
err_context = {'other_branch': other_branch}
2909
response = self._call('Branch.set_last_revision_ex',
2910
self._remote_path(), self._lock_token, self._repo_lock_token,
2911
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2913
self._clear_cached_state()
2914
if len(response) != 3 and response[0] != 'ok':
2915
raise errors.UnexpectedSmartServerResponse(response)
2916
new_revno, new_revision_id = response[1:]
2917
self._last_revision_info_cache = new_revno, new_revision_id
2918
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2919
if self._real_branch is not None:
2920
cache = new_revno, new_revision_id
2921
self._real_branch._last_revision_info_cache = cache
2923
def _set_last_revision(self, revision_id):
2924
old_revno, old_revid = self.last_revision_info()
2925
# This performs additional work to meet the hook contract; while its
2926
# undesirable, we have to synthesise the revno to call the hook, and
2927
# not calling the hook is worse as it means changes can't be prevented.
2928
# Having calculated this though, we can't just call into
2929
# set_last_revision_info as a simple call, because there is a set_rh
2930
# hook that some folk may still be using.
2931
history = self._lefthand_history(revision_id)
2932
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2933
self._clear_cached_state()
2934
response = self._call('Branch.set_last_revision',
2935
self._remote_path(), self._lock_token, self._repo_lock_token,
2937
if response != ('ok',):
2938
raise errors.UnexpectedSmartServerResponse(response)
2939
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2941
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2943
def set_revision_history(self, rev_history):
2944
"""See Branch.set_revision_history."""
2945
self._set_revision_history(rev_history)
2948
def _set_revision_history(self, rev_history):
2949
# Send just the tip revision of the history; the server will generate
2950
# the full history from that. If the revision doesn't exist in this
2951
# branch, NoSuchRevision will be raised.
2952
if rev_history == []:
2955
rev_id = rev_history[-1]
2956
self._set_last_revision(rev_id)
2957
for hook in branch.Branch.hooks['set_rh']:
2958
hook(self, rev_history)
2959
self._cache_revision_history(rev_history)
2961
def _get_parent_location(self):
2962
medium = self._client._medium
2963
if medium._is_remote_before((1, 13)):
2964
return self._vfs_get_parent_location()
2966
response = self._call('Branch.get_parent', self._remote_path())
2967
except errors.UnknownSmartMethod:
2968
medium._remember_remote_is_before((1, 13))
2969
return self._vfs_get_parent_location()
2970
if len(response) != 1:
2971
raise errors.UnexpectedSmartServerResponse(response)
2972
parent_location = response[0]
2973
if parent_location == '':
2975
return parent_location
2977
def _vfs_get_parent_location(self):
2979
return self._real_branch._get_parent_location()
2981
def _set_parent_location(self, url):
2982
medium = self._client._medium
2983
if medium._is_remote_before((1, 15)):
2984
return self._vfs_set_parent_location(url)
2986
call_url = url or ''
2987
if type(call_url) is not str:
2988
raise AssertionError('url must be a str or None (%s)' % url)
2989
response = self._call('Branch.set_parent_location',
2990
self._remote_path(), self._lock_token, self._repo_lock_token,
2992
except errors.UnknownSmartMethod:
2993
medium._remember_remote_is_before((1, 15))
2994
return self._vfs_set_parent_location(url)
2996
raise errors.UnexpectedSmartServerResponse(response)
2998
def _vfs_set_parent_location(self, url):
3000
return self._real_branch._set_parent_location(url)
3003
def pull(self, source, overwrite=False, stop_revision=None,
3005
self._clear_cached_state_of_remote_branch_only()
3007
return self._real_branch.pull(
3008
source, overwrite=overwrite, stop_revision=stop_revision,
3009
_override_hook_target=self, **kwargs)
3012
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3014
return self._real_branch.push(
3015
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3016
_override_hook_source_branch=self)
3018
def is_locked(self):
3019
return self._lock_count >= 1
3022
def revision_id_to_revno(self, revision_id):
3024
return self._real_branch.revision_id_to_revno(revision_id)
3027
def set_last_revision_info(self, revno, revision_id):
3028
# XXX: These should be returned by the set_last_revision_info verb
3029
old_revno, old_revid = self.last_revision_info()
3030
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3031
if not revision_id or not isinstance(revision_id, basestring):
3032
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3034
response = self._call('Branch.set_last_revision_info',
3035
self._remote_path(), self._lock_token, self._repo_lock_token,
3036
str(revno), revision_id)
3037
except errors.UnknownSmartMethod:
3039
self._clear_cached_state_of_remote_branch_only()
3040
self._real_branch.set_last_revision_info(revno, revision_id)
3041
self._last_revision_info_cache = revno, revision_id
3043
if response == ('ok',):
3044
self._clear_cached_state()
3045
self._last_revision_info_cache = revno, revision_id
3046
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3047
# Update the _real_branch's cache too.
3048
if self._real_branch is not None:
3049
cache = self._last_revision_info_cache
3050
self._real_branch._last_revision_info_cache = cache
3052
raise errors.UnexpectedSmartServerResponse(response)
3055
def generate_revision_history(self, revision_id, last_rev=None,
3057
medium = self._client._medium
3058
if not medium._is_remote_before((1, 6)):
3059
# Use a smart method for 1.6 and above servers
3061
self._set_last_revision_descendant(revision_id, other_branch,
3062
allow_diverged=True, allow_overwrite_descendant=True)
3064
except errors.UnknownSmartMethod:
3065
medium._remember_remote_is_before((1, 6))
3066
self._clear_cached_state_of_remote_branch_only()
3067
self._set_revision_history(self._lefthand_history(revision_id,
3068
last_rev=last_rev,other_branch=other_branch))
3070
def set_push_location(self, location):
3072
return self._real_branch.set_push_location(location)
3074
def heads_to_fetch(self):
3075
if self._format._use_default_local_heads_to_fetch():
3076
# We recognise this format, and its heads-to-fetch implementation
3077
# is the default one (tip + tags). In this case it's cheaper to
3078
# just use the default implementation rather than a special RPC as
3079
# the tip and tags data is cached.
3080
return branch.Branch.heads_to_fetch(self)
3081
medium = self._client._medium
3082
if medium._is_remote_before((2, 4)):
3083
return self._vfs_heads_to_fetch()
3085
return self._rpc_heads_to_fetch()
3086
except errors.UnknownSmartMethod:
3087
medium._remember_remote_is_before((2, 4))
3088
return self._vfs_heads_to_fetch()
3090
def _rpc_heads_to_fetch(self):
3091
response = self._call('Branch.heads_to_fetch', self._remote_path())
3092
if len(response) != 2:
3093
raise errors.UnexpectedSmartServerResponse(response)
3094
must_fetch, if_present_fetch = response
3095
return set(must_fetch), set(if_present_fetch)
3097
def _vfs_heads_to_fetch(self):
3099
return self._real_branch.heads_to_fetch()
3102
class RemoteConfig(object):
3103
"""A Config that reads and writes from smart verbs.
3105
It is a low-level object that considers config data to be name/value pairs
3106
that may be associated with a section. Assigning meaning to the these
3107
values is done at higher levels like bzrlib.config.TreeConfig.
3110
def get_option(self, name, section=None, default=None):
3111
"""Return the value associated with a named option.
3113
:param name: The name of the value
3114
:param section: The section the option is in (if any)
3115
:param default: The value to return if the value is not set
3116
:return: The value or default value
3119
configobj = self._get_configobj()
3122
section_obj = configobj
3125
section_obj = configobj[section]
3128
if section_obj is None:
3131
value = section_obj.get(name, default)
3132
except errors.UnknownSmartMethod:
3133
value = self._vfs_get_option(name, section, default)
3134
for hook in config.OldConfigHooks['get']:
3135
hook(self, name, value)
3138
def _response_to_configobj(self, response):
3139
if len(response[0]) and response[0][0] != 'ok':
3140
raise errors.UnexpectedSmartServerResponse(response)
3141
lines = response[1].read_body_bytes().splitlines()
3142
conf = config.ConfigObj(lines, encoding='utf-8')
3143
for hook in config.OldConfigHooks['load']:
3148
class RemoteBranchConfig(RemoteConfig):
3149
"""A RemoteConfig for Branches."""
3151
def __init__(self, branch):
3152
self._branch = branch
3154
def _get_configobj(self):
3155
path = self._branch._remote_path()
3156
response = self._branch._client.call_expecting_body(
3157
'Branch.get_config_file', path)
3158
return self._response_to_configobj(response)
3160
def set_option(self, value, name, section=None):
3161
"""Set the value associated with a named option.
3163
:param value: The value to set
3164
:param name: The name of the value to set
3165
:param section: The section the option is in (if any)
3167
medium = self._branch._client._medium
3168
if medium._is_remote_before((1, 14)):
3169
return self._vfs_set_option(value, name, section)
3170
if isinstance(value, dict):
3171
if medium._is_remote_before((2, 2)):
3172
return self._vfs_set_option(value, name, section)
3173
return self._set_config_option_dict(value, name, section)
3175
return self._set_config_option(value, name, section)
3177
def _set_config_option(self, value, name, section):
3179
path = self._branch._remote_path()
3180
response = self._branch._client.call('Branch.set_config_option',
3181
path, self._branch._lock_token, self._branch._repo_lock_token,
3182
value.encode('utf8'), name, section or '')
3183
except errors.UnknownSmartMethod:
3184
medium = self._branch._client._medium
3185
medium._remember_remote_is_before((1, 14))
3186
return self._vfs_set_option(value, name, section)
3188
raise errors.UnexpectedSmartServerResponse(response)
3190
def _serialize_option_dict(self, option_dict):
3192
for key, value in option_dict.items():
3193
if isinstance(key, unicode):
3194
key = key.encode('utf8')
3195
if isinstance(value, unicode):
3196
value = value.encode('utf8')
3197
utf8_dict[key] = value
3198
return bencode.bencode(utf8_dict)
3200
def _set_config_option_dict(self, value, name, section):
3202
path = self._branch._remote_path()
3203
serialised_dict = self._serialize_option_dict(value)
3204
response = self._branch._client.call(
3205
'Branch.set_config_option_dict',
3206
path, self._branch._lock_token, self._branch._repo_lock_token,
3207
serialised_dict, name, section or '')
3208
except errors.UnknownSmartMethod:
3209
medium = self._branch._client._medium
3210
medium._remember_remote_is_before((2, 2))
3211
return self._vfs_set_option(value, name, section)
3213
raise errors.UnexpectedSmartServerResponse(response)
3215
def _real_object(self):
3216
self._branch._ensure_real()
3217
return self._branch._real_branch
3219
def _vfs_set_option(self, value, name, section=None):
3220
return self._real_object()._get_config().set_option(
3221
value, name, section)
3224
class RemoteBzrDirConfig(RemoteConfig):
3225
"""A RemoteConfig for BzrDirs."""
3227
def __init__(self, bzrdir):
3228
self._bzrdir = bzrdir
3230
def _get_configobj(self):
3231
medium = self._bzrdir._client._medium
3232
verb = 'BzrDir.get_config_file'
3233
if medium._is_remote_before((1, 15)):
3234
raise errors.UnknownSmartMethod(verb)
3235
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3236
response = self._bzrdir._call_expecting_body(
3238
return self._response_to_configobj(response)
3240
def _vfs_get_option(self, name, section, default):
3241
return self._real_object()._get_config().get_option(
3242
name, section, default)
3244
def set_option(self, value, name, section=None):
3245
"""Set the value associated with a named option.
3247
:param value: The value to set
3248
:param name: The name of the value to set
3249
:param section: The section the option is in (if any)
3251
return self._real_object()._get_config().set_option(
3252
value, name, section)
3254
def _real_object(self):
3255
self._bzrdir._ensure_real()
3256
return self._bzrdir._real_bzrdir
3260
def _extract_tar(tar, to_dir):
3261
"""Extract all the contents of a tarfile object.
3263
A replacement for extractall, which is not present in python2.4
3266
tar.extract(tarinfo, to_dir)
3269
def _translate_error(err, **context):
3270
"""Translate an ErrorFromSmartServer into a more useful error.
3272
Possible context keys:
3280
If the error from the server doesn't match a known pattern, then
3281
UnknownErrorFromSmartServer is raised.
3285
return context[name]
3286
except KeyError, key_err:
3287
mutter('Missing key %r in context %r', key_err.args[0], context)
3290
"""Get the path from the context if present, otherwise use first error
3294
return context['path']
3295
except KeyError, key_err:
3297
return err.error_args[0]
3298
except IndexError, idx_err:
3300
'Missing key %r in context %r', key_err.args[0], context)
3303
if err.error_verb == 'NoSuchRevision':
3304
raise NoSuchRevision(find('branch'), err.error_args[0])
3305
elif err.error_verb == 'nosuchrevision':
3306
raise NoSuchRevision(find('repository'), err.error_args[0])
3307
elif err.error_verb == 'nobranch':
3308
if len(err.error_args) >= 1:
3309
extra = err.error_args[0]
3312
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3314
elif err.error_verb == 'norepository':
3315
raise errors.NoRepositoryPresent(find('bzrdir'))
3316
elif err.error_verb == 'UnlockableTransport':
3317
raise errors.UnlockableTransport(find('bzrdir').root_transport)
3318
elif err.error_verb == 'TokenMismatch':
3319
raise errors.TokenMismatch(find('token'), '(remote token)')
3320
elif err.error_verb == 'Diverged':
3321
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3322
elif err.error_verb == 'NotStacked':
3323
raise errors.NotStacked(branch=find('branch'))
3324
elif err.error_verb == 'PermissionDenied':
3326
if len(err.error_args) >= 2:
3327
extra = err.error_args[1]
3330
raise errors.PermissionDenied(path, extra=extra)
3331
elif err.error_verb == 'ReadError':
3333
raise errors.ReadError(path)
3334
elif err.error_verb == 'NoSuchFile':
3336
raise errors.NoSuchFile(path)
3337
_translate_error_without_context(err)
3340
def _translate_error_without_context(err):
3341
"""Translate any ErrorFromSmartServer values that don't require context"""
3342
if err.error_verb == 'IncompatibleRepositories':
3343
raise errors.IncompatibleRepositories(err.error_args[0],
3344
err.error_args[1], err.error_args[2])
3345
elif err.error_verb == 'LockContention':
3346
raise errors.LockContention('(remote lock)')
3347
elif err.error_verb == 'LockFailed':
3348
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3349
elif err.error_verb == 'TipChangeRejected':
3350
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3351
elif err.error_verb == 'UnstackableBranchFormat':
3352
raise errors.UnstackableBranchFormat(*err.error_args)
3353
elif err.error_verb == 'UnstackableRepositoryFormat':
3354
raise errors.UnstackableRepositoryFormat(*err.error_args)
3355
elif err.error_verb == 'FileExists':
3356
raise errors.FileExists(err.error_args[0])
3357
elif err.error_verb == 'DirectoryNotEmpty':
3358
raise errors.DirectoryNotEmpty(err.error_args[0])
3359
elif err.error_verb == 'ShortReadvError':
3360
args = err.error_args
3361
raise errors.ShortReadvError(
3362
args[0], int(args[1]), int(args[2]), int(args[3]))
3363
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3364
encoding = str(err.error_args[0]) # encoding must always be a string
3365
val = err.error_args[1]
3366
start = int(err.error_args[2])
3367
end = int(err.error_args[3])
3368
reason = str(err.error_args[4]) # reason must always be a string
3369
if val.startswith('u:'):
3370
val = val[2:].decode('utf-8')
3371
elif val.startswith('s:'):
3372
val = val[2:].decode('base64')
3373
if err.error_verb == 'UnicodeDecodeError':
3374
raise UnicodeDecodeError(encoding, val, start, end, reason)
3375
elif err.error_verb == 'UnicodeEncodeError':
3376
raise UnicodeEncodeError(encoding, val, start, end, reason)
3377
elif err.error_verb == 'ReadOnlyError':
3378
raise errors.TransportNotPossible('readonly transport')
3379
elif err.error_verb == 'MemoryError':
3380
raise errors.BzrError("remote server out of memory\n"
3381
"Retry non-remotely, or contact the server admin for details.")
3382
raise errors.UnknownErrorFromSmartServer(err)