1
# Copyright (C) 2006-2012 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
27
config as _mod_config,
37
repository as _mod_repository,
38
revision as _mod_revision,
43
bzrdir as _mod_bzrdir,
45
testament as _mod_testament,
49
from .branch import BranchReferenceFormat
50
from ..branch import BranchWriteLockResult
51
from ..decorators import only_raises
52
from ..errors import (
56
from ..i18n import gettext
57
from .inventory import Inventory
58
from .inventorytree import InventoryRevisionTree
59
from ..lockable_files import LockableFiles
60
from .smart import client, vfs, repository as smart_repo
61
from .smart.client import _SmartClient
62
from ..revision import NULL_REVISION
63
from ..repository import RepositoryWriteLockResult, _LazyListJoin
64
from .serializer import format_registry as serializer_format_registry
65
from ..trace import mutter, note, warning, log_exception_quietly
66
from .versionedfile import FulltextContentFactory
69
_DEFAULT_SEARCH_DEPTH = 100
72
class _RpcHelper(object):
73
"""Mixin class that helps with issuing RPCs."""
75
def _call(self, method, *args, **err_context):
77
return self._client.call(method, *args)
78
except errors.ErrorFromSmartServer as err:
79
self._translate_error(err, **err_context)
81
def _call_expecting_body(self, method, *args, **err_context):
83
return self._client.call_expecting_body(method, *args)
84
except errors.ErrorFromSmartServer as err:
85
self._translate_error(err, **err_context)
87
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
89
return self._client.call_with_body_bytes(method, args, body_bytes)
90
except errors.ErrorFromSmartServer as err:
91
self._translate_error(err, **err_context)
93
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
96
return self._client.call_with_body_bytes_expecting_body(
97
method, args, body_bytes)
98
except errors.ErrorFromSmartServer as err:
99
self._translate_error(err, **err_context)
102
def response_tuple_to_repo_format(response):
103
"""Convert a response tuple describing a repository format to a format."""
104
format = RemoteRepositoryFormat()
105
format._rich_root_data = (response[0] == b'yes')
106
format._supports_tree_reference = (response[1] == b'yes')
107
format._supports_external_lookups = (response[2] == b'yes')
108
format._network_name = response[3]
112
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.bzr.remote
113
# does not have to be imported unless a remote format is involved.
115
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
116
"""Format representing bzrdirs accessed via a smart server"""
118
supports_workingtrees = False
120
colocated_branches = False
123
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
124
# XXX: It's a bit ugly that the network name is here, because we'd
125
# like to believe that format objects are stateless or at least
126
# immutable, However, we do at least avoid mutating the name after
127
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
128
self._network_name = None
131
return "%s(_network_name=%r)" % (self.__class__.__name__,
134
def get_format_description(self):
135
if self._network_name:
137
real_format = controldir.network_format_registry.get(
142
return 'Remote: ' + real_format.get_format_description()
143
return 'bzr remote bzrdir'
145
def get_format_string(self):
146
raise NotImplementedError(self.get_format_string)
148
def network_name(self):
149
if self._network_name:
150
return self._network_name
152
raise AssertionError("No network name set.")
154
def initialize_on_transport(self, transport):
156
# hand off the request to the smart server
157
client_medium = transport.get_smart_medium()
158
except errors.NoSmartMedium:
159
# TODO: lookup the local format from a server hint.
160
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
161
return local_dir_format.initialize_on_transport(transport)
162
client = _SmartClient(client_medium)
163
path = client.remote_path_from_transport(transport)
165
response = client.call(b'BzrDirFormat.initialize', path)
166
except errors.ErrorFromSmartServer as err:
167
_translate_error(err, path=path)
168
if response[0] != b'ok':
169
raise errors.SmartProtocolError(
170
'unexpected response code %s' % (response,))
171
format = RemoteBzrDirFormat()
172
self._supply_sub_formats_to(format)
173
return RemoteBzrDir(transport, format)
175
def parse_NoneTrueFalse(self, arg):
182
raise AssertionError("invalid arg %r" % arg)
184
def _serialize_NoneTrueFalse(self, arg):
191
def _serialize_NoneString(self, arg):
194
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
195
create_prefix=False, force_new_repo=False, stacked_on=None,
196
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
199
# hand off the request to the smart server
200
client_medium = transport.get_smart_medium()
201
except errors.NoSmartMedium:
204
# Decline to open it if the server doesn't support our required
205
# version (3) so that the VFS-based transport will do it.
206
if client_medium.should_probe():
208
server_version = client_medium.protocol_version()
209
if server_version != '2':
213
except errors.SmartProtocolError:
214
# Apparently there's no usable smart server there, even though
215
# the medium supports the smart protocol.
220
client = _SmartClient(client_medium)
221
path = client.remote_path_from_transport(transport)
222
if client_medium._is_remote_before((1, 16)):
225
# TODO: lookup the local format from a server hint.
226
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
227
self._supply_sub_formats_to(local_dir_format)
228
return local_dir_format.initialize_on_transport_ex(transport,
229
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
230
force_new_repo=force_new_repo, stacked_on=stacked_on,
231
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
232
make_working_trees=make_working_trees, shared_repo=shared_repo,
234
return self._initialize_on_transport_ex_rpc(client, path, transport,
235
use_existing_dir, create_prefix, force_new_repo, stacked_on,
236
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
238
def _initialize_on_transport_ex_rpc(self, client, path, transport,
239
use_existing_dir, create_prefix, force_new_repo, stacked_on,
240
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
242
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
243
args.append(self._serialize_NoneTrueFalse(create_prefix))
244
args.append(self._serialize_NoneTrueFalse(force_new_repo))
245
args.append(self._serialize_NoneString(stacked_on))
246
# stack_on_pwd is often/usually our transport
249
stack_on_pwd = transport.relpath(stack_on_pwd).encode('utf-8')
252
except errors.PathNotChild:
254
args.append(self._serialize_NoneString(stack_on_pwd))
255
args.append(self._serialize_NoneString(repo_format_name))
256
args.append(self._serialize_NoneTrueFalse(make_working_trees))
257
args.append(self._serialize_NoneTrueFalse(shared_repo))
258
request_network_name = self._network_name or \
259
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
261
response = client.call(b'BzrDirFormat.initialize_ex_1.16',
262
request_network_name, path, *args)
263
except errors.UnknownSmartMethod:
264
client._medium._remember_remote_is_before((1, 16))
265
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
266
self._supply_sub_formats_to(local_dir_format)
267
return local_dir_format.initialize_on_transport_ex(transport,
268
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
269
force_new_repo=force_new_repo, stacked_on=stacked_on,
270
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
271
make_working_trees=make_working_trees, shared_repo=shared_repo,
273
except errors.ErrorFromSmartServer as err:
274
_translate_error(err, path=path.decode('utf-8'))
275
repo_path = response[0]
276
bzrdir_name = response[6]
277
require_stacking = response[7]
278
require_stacking = self.parse_NoneTrueFalse(require_stacking)
279
format = RemoteBzrDirFormat()
280
format._network_name = bzrdir_name
281
self._supply_sub_formats_to(format)
282
bzrdir = RemoteBzrDir(transport, format, _client=client)
284
repo_format = response_tuple_to_repo_format(response[1:])
285
if repo_path == b'.':
287
repo_path = repo_path.decode('utf-8')
289
repo_bzrdir_format = RemoteBzrDirFormat()
290
repo_bzrdir_format._network_name = response[5]
291
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
295
final_stack = response[8] or None
297
final_stack = final_stack.decode('utf-8')
298
final_stack_pwd = response[9] or None
300
final_stack_pwd = urlutils.join(
301
transport.base, final_stack_pwd.decode('utf-8'))
302
remote_repo = RemoteRepository(repo_bzr, repo_format)
303
if len(response) > 10:
304
# Updated server verb that locks remotely.
305
repo_lock_token = response[10] or None
306
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
308
remote_repo.dont_leave_lock_in_place()
310
remote_repo.lock_write()
311
policy = _mod_bzrdir.UseExistingRepository(remote_repo,
312
final_stack, final_stack_pwd, require_stacking)
313
policy.acquire_repository()
317
bzrdir._format.set_branch_format(self.get_branch_format())
319
# The repo has already been created, but we need to make sure that
320
# we'll make a stackable branch.
321
bzrdir._format.require_stacking(_skip_repo=True)
322
return remote_repo, bzrdir, require_stacking, policy
324
def _open(self, transport):
325
return RemoteBzrDir(transport, self)
327
def __eq__(self, other):
328
if not isinstance(other, RemoteBzrDirFormat):
330
return self.get_format_description() == other.get_format_description()
332
def __return_repository_format(self):
333
# Always return a RemoteRepositoryFormat object, but if a specific bzr
334
# repository format has been asked for, tell the RemoteRepositoryFormat
335
# that it should use that for init() etc.
336
result = RemoteRepositoryFormat()
337
custom_format = getattr(self, '_repository_format', None)
339
if isinstance(custom_format, RemoteRepositoryFormat):
342
# We will use the custom format to create repositories over the
343
# wire; expose its details like rich_root_data for code to
345
result._custom_format = custom_format
348
def get_branch_format(self):
349
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
350
if not isinstance(result, RemoteBranchFormat):
351
new_result = RemoteBranchFormat()
352
new_result._custom_format = result
354
self.set_branch_format(new_result)
358
repository_format = property(__return_repository_format,
359
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) # .im_func)
362
class RemoteControlStore(_mod_config.IniFileStore):
363
"""Control store which attempts to use HPSS calls to retrieve control store.
365
Note that this is specific to bzr-based formats.
368
def __init__(self, bzrdir):
369
super(RemoteControlStore, self).__init__()
370
self.controldir = bzrdir
371
self._real_store = None
373
def lock_write(self, token=None):
375
return self._real_store.lock_write(token)
379
return self._real_store.unlock()
382
with self.lock_write():
383
# We need to be able to override the undecorated implementation
384
self.save_without_locking()
386
def save_without_locking(self):
387
super(RemoteControlStore, self).save()
389
def _ensure_real(self):
390
self.controldir._ensure_real()
391
if self._real_store is None:
392
self._real_store = _mod_config.ControlStore(self.controldir)
394
def external_url(self):
395
return urlutils.join(self.branch.user_url, 'control.conf')
397
def _load_content(self):
398
medium = self.controldir._client._medium
399
path = self.controldir._path_for_remote_call(self.controldir._client)
401
response, handler = self.controldir._call_expecting_body(
402
b'BzrDir.get_config_file', path)
403
except errors.UnknownSmartMethod:
405
return self._real_store._load_content()
406
if len(response) and response[0] != b'ok':
407
raise errors.UnexpectedSmartServerResponse(response)
408
return handler.read_body_bytes()
410
def _save_content(self, content):
411
# FIXME JRV 2011-11-22: Ideally this should use a
412
# HPSS call too, but at the moment it is not possible
413
# to write lock control directories.
415
return self._real_store._save_content(content)
418
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
419
"""Control directory on a remote server, accessed via bzr:// or similar."""
421
def __init__(self, transport, format, _client=None, _force_probe=False):
422
"""Construct a RemoteBzrDir.
424
:param _client: Private parameter for testing. Disables probing and the
425
use of a real bzrdir.
427
_mod_bzrdir.BzrDir.__init__(self, transport, format)
428
# this object holds a delegated bzrdir that uses file-level operations
429
# to talk to the other side
430
self._real_bzrdir = None
431
self._has_working_tree = None
432
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
433
# create_branch for details.
434
self._next_open_branch_result = None
437
medium = transport.get_smart_medium()
438
self._client = client._SmartClient(medium)
440
self._client = _client
447
return '%s(%r)' % (self.__class__.__name__, self._client)
449
def _probe_bzrdir(self):
450
medium = self._client._medium
451
path = self._path_for_remote_call(self._client)
452
if medium._is_remote_before((2, 1)):
456
self._rpc_open_2_1(path)
458
except errors.UnknownSmartMethod:
459
medium._remember_remote_is_before((2, 1))
462
def _rpc_open_2_1(self, path):
463
response = self._call(b'BzrDir.open_2.1', path)
464
if response == (b'no',):
465
raise errors.NotBranchError(path=self.root_transport.base)
466
elif response[0] == b'yes':
467
if response[1] == b'yes':
468
self._has_working_tree = True
469
elif response[1] == b'no':
470
self._has_working_tree = False
472
raise errors.UnexpectedSmartServerResponse(response)
474
raise errors.UnexpectedSmartServerResponse(response)
476
def _rpc_open(self, path):
477
response = self._call(b'BzrDir.open', path)
478
if response not in [(b'yes',), (b'no',)]:
479
raise errors.UnexpectedSmartServerResponse(response)
480
if response == (b'no',):
481
raise errors.NotBranchError(path=self.root_transport.base)
483
def _ensure_real(self):
484
"""Ensure that there is a _real_bzrdir set.
486
Used before calls to self._real_bzrdir.
488
if not self._real_bzrdir:
489
if 'hpssvfs' in debug.debug_flags:
491
warning('VFS BzrDir access triggered\n%s',
492
''.join(traceback.format_stack()))
493
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
494
self.root_transport, probers=[_mod_bzr.BzrProber])
495
self._format._network_name = \
496
self._real_bzrdir._format.network_name()
498
def _translate_error(self, err, **context):
499
_translate_error(err, bzrdir=self, **context)
501
def break_lock(self):
502
# Prevent aliasing problems in the next_open_branch_result cache.
503
# See create_branch for rationale.
504
self._next_open_branch_result = None
505
return _mod_bzrdir.BzrDir.break_lock(self)
507
def _vfs_checkout_metadir(self):
509
return self._real_bzrdir.checkout_metadir()
511
def checkout_metadir(self):
512
"""Retrieve the controldir format to use for checkouts of this one.
514
medium = self._client._medium
515
if medium._is_remote_before((2, 5)):
516
return self._vfs_checkout_metadir()
517
path = self._path_for_remote_call(self._client)
519
response = self._client.call(b'BzrDir.checkout_metadir',
521
except errors.UnknownSmartMethod:
522
medium._remember_remote_is_before((2, 5))
523
return self._vfs_checkout_metadir()
524
if len(response) != 3:
525
raise errors.UnexpectedSmartServerResponse(response)
526
control_name, repo_name, branch_name = response
528
format = controldir.network_format_registry.get(control_name)
530
raise errors.UnknownFormatError(kind='control',
534
repo_format = _mod_repository.network_format_registry.get(
537
raise errors.UnknownFormatError(kind='repository',
539
format.repository_format = repo_format
542
format.set_branch_format(
543
branch.network_format_registry.get(branch_name))
545
raise errors.UnknownFormatError(kind='branch',
549
def _vfs_cloning_metadir(self, require_stacking=False):
551
return self._real_bzrdir.cloning_metadir(
552
require_stacking=require_stacking)
554
def cloning_metadir(self, require_stacking=False):
555
medium = self._client._medium
556
if medium._is_remote_before((1, 13)):
557
return self._vfs_cloning_metadir(require_stacking=require_stacking)
558
verb = b'BzrDir.cloning_metadir'
563
path = self._path_for_remote_call(self._client)
565
response = self._call(verb, path, stacking)
566
except errors.UnknownSmartMethod:
567
medium._remember_remote_is_before((1, 13))
568
return self._vfs_cloning_metadir(require_stacking=require_stacking)
569
except errors.UnknownErrorFromSmartServer as err:
570
if err.error_tuple != (b'BranchReference',):
572
# We need to resolve the branch reference to determine the
573
# cloning_metadir. This causes unnecessary RPCs to open the
574
# referenced branch (and bzrdir, etc) but only when the caller
575
# didn't already resolve the branch reference.
576
referenced_branch = self.open_branch()
577
return referenced_branch.controldir.cloning_metadir()
578
if len(response) != 3:
579
raise errors.UnexpectedSmartServerResponse(response)
580
control_name, repo_name, branch_info = response
581
if len(branch_info) != 2:
582
raise errors.UnexpectedSmartServerResponse(response)
583
branch_ref, branch_name = branch_info
585
format = controldir.network_format_registry.get(control_name)
587
raise errors.UnknownFormatError(
588
kind='control', format=control_name)
592
format.repository_format = _mod_repository.network_format_registry.get(
595
raise errors.UnknownFormatError(kind='repository',
597
if branch_ref == b'ref':
598
# XXX: we need possible_transports here to avoid reopening the
599
# connection to the referenced location
600
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
601
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
602
format.set_branch_format(branch_format)
603
elif branch_ref == b'branch':
606
branch_format = branch.network_format_registry.get(
609
raise errors.UnknownFormatError(kind='branch',
611
format.set_branch_format(branch_format)
613
raise errors.UnexpectedSmartServerResponse(response)
616
def create_repository(self, shared=False):
617
# as per meta1 formats - just delegate to the format object which may
619
result = self._format.repository_format.initialize(self, shared)
620
if not isinstance(result, RemoteRepository):
621
return self.open_repository()
625
def destroy_repository(self):
626
"""See BzrDir.destroy_repository"""
627
path = self._path_for_remote_call(self._client)
629
response = self._call(b'BzrDir.destroy_repository', path)
630
except errors.UnknownSmartMethod:
632
self._real_bzrdir.destroy_repository()
634
if response[0] != b'ok':
635
raise SmartProtocolError(
636
'unexpected response code %s' % (response,))
638
def create_branch(self, name=None, repository=None,
639
append_revisions_only=None):
641
name = self._get_selected_branch()
643
raise errors.NoColocatedBranchSupport(self)
644
# as per meta1 formats - just delegate to the format object which may
646
real_branch = self._format.get_branch_format().initialize(self,
647
name=name, repository=repository,
648
append_revisions_only=append_revisions_only)
649
if not isinstance(real_branch, RemoteBranch):
650
if not isinstance(repository, RemoteRepository):
651
raise AssertionError(
652
'need a RemoteRepository to use with RemoteBranch, got %r'
654
result = RemoteBranch(self, repository, real_branch, name=name)
657
# BzrDir.clone_on_transport() uses the result of create_branch but does
658
# not return it to its callers; we save approximately 8% of our round
659
# trips by handing the branch we created back to the first caller to
660
# open_branch rather than probing anew. Long term we need a API in
661
# bzrdir that doesn't discard result objects (like result_branch).
663
self._next_open_branch_result = result
666
def destroy_branch(self, name=None):
667
"""See BzrDir.destroy_branch"""
669
name = self._get_selected_branch()
671
raise errors.NoColocatedBranchSupport(self)
672
path = self._path_for_remote_call(self._client)
678
response = self._call(b'BzrDir.destroy_branch', path, *args)
679
except errors.UnknownSmartMethod:
681
self._real_bzrdir.destroy_branch(name=name)
682
self._next_open_branch_result = None
684
self._next_open_branch_result = None
685
if response[0] != b'ok':
686
raise SmartProtocolError(
687
'unexpected response code %s' % (response,))
689
def create_workingtree(self, revision_id=None, from_branch=None,
690
accelerator_tree=None, hardlink=False):
691
raise errors.NotLocalUrl(self.transport.base)
693
def find_branch_format(self, name=None):
694
"""Find the branch 'format' for this bzrdir.
696
This might be a synthetic object for e.g. RemoteBranch and SVN.
698
b = self.open_branch(name=name)
701
def branch_names(self):
702
path = self._path_for_remote_call(self._client)
704
response, handler = self._call_expecting_body(
705
b'BzrDir.get_branches', path)
706
except errors.UnknownSmartMethod:
708
return self._real_bzrdir.branch_names()
709
if response[0] != b"success":
710
raise errors.UnexpectedSmartServerResponse(response)
711
body = bencode.bdecode(handler.read_body_bytes())
713
for name, value in body.items():
714
name = name.decode('utf-8')
718
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
719
path = self._path_for_remote_call(self._client)
721
response, handler = self._call_expecting_body(
722
b'BzrDir.get_branches', path)
723
except errors.UnknownSmartMethod:
725
return self._real_bzrdir.get_branches()
726
if response[0] != b"success":
727
raise errors.UnexpectedSmartServerResponse(response)
728
body = bencode.bdecode(handler.read_body_bytes())
730
for name, value in body.items():
731
name = name.decode('utf-8')
732
ret[name] = self._open_branch(
733
name, value[0].decode('ascii'), value[1],
734
possible_transports=possible_transports,
735
ignore_fallbacks=ignore_fallbacks)
738
def set_branch_reference(self, target_branch, name=None):
739
"""See BzrDir.set_branch_reference()."""
741
name = self._get_selected_branch()
743
raise errors.NoColocatedBranchSupport(self)
745
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
747
def get_branch_reference(self, name=None):
748
"""See BzrDir.get_branch_reference()."""
750
name = self._get_selected_branch()
752
raise errors.NoColocatedBranchSupport(self)
753
response = self._get_branch_reference()
754
if response[0] == 'ref':
755
return response[1].decode('utf-8')
759
def _get_branch_reference(self):
760
"""Get branch reference information
762
:return: Tuple with (kind, location_or_format)
763
if kind == 'ref', then location_or_format contains a location
764
otherwise, it contains a format name
766
path = self._path_for_remote_call(self._client)
767
medium = self._client._medium
769
(b'BzrDir.open_branchV3', (2, 1)),
770
(b'BzrDir.open_branchV2', (1, 13)),
771
(b'BzrDir.open_branch', None),
773
for verb, required_version in candidate_calls:
774
if required_version and medium._is_remote_before(required_version):
777
response = self._call(verb, path)
778
except errors.UnknownSmartMethod:
779
if required_version is None:
781
medium._remember_remote_is_before(required_version)
784
if verb == b'BzrDir.open_branch':
785
if response[0] != b'ok':
786
raise errors.UnexpectedSmartServerResponse(response)
787
if response[1] != b'':
788
return ('ref', response[1])
790
return ('branch', b'')
791
if response[0] not in (b'ref', b'branch'):
792
raise errors.UnexpectedSmartServerResponse(response)
793
return (response[0].decode('ascii'), response[1])
795
def _get_tree_branch(self, name=None):
796
"""See BzrDir._get_tree_branch()."""
797
return None, self.open_branch(name=name)
799
def _open_branch(self, name, kind, location_or_format,
800
ignore_fallbacks=False, possible_transports=None):
802
# a branch reference, use the existing BranchReference logic.
803
format = BranchReferenceFormat()
804
ref_loc = urlutils.join(self.user_url, location_or_format.decode('utf-8'))
805
return format.open(self, name=name, _found=True,
807
ignore_fallbacks=ignore_fallbacks,
808
possible_transports=possible_transports)
809
branch_format_name = location_or_format
810
if not branch_format_name:
811
branch_format_name = None
812
format = RemoteBranchFormat(network_name=branch_format_name)
813
return RemoteBranch(self, self.find_repository(), format=format,
814
setup_stacking=not ignore_fallbacks, name=name,
815
possible_transports=possible_transports)
817
def open_branch(self, name=None, unsupported=False,
818
ignore_fallbacks=False, possible_transports=None):
820
name = self._get_selected_branch()
822
raise errors.NoColocatedBranchSupport(self)
824
raise NotImplementedError(
825
'unsupported flag support not implemented yet.')
826
if self._next_open_branch_result is not None:
827
# See create_branch for details.
828
result = self._next_open_branch_result
829
self._next_open_branch_result = None
831
response = self._get_branch_reference()
832
return self._open_branch(name, response[0], response[1],
833
possible_transports=possible_transports,
834
ignore_fallbacks=ignore_fallbacks)
836
def _open_repo_v1(self, path):
837
verb = b'BzrDir.find_repository'
838
response = self._call(verb, path)
839
if response[0] != b'ok':
840
raise errors.UnexpectedSmartServerResponse(response)
841
# servers that only support the v1 method don't support external
844
repo = self._real_bzrdir.open_repository()
845
response = response + (b'no', repo._format.network_name())
846
return response, repo
848
def _open_repo_v2(self, path):
849
verb = b'BzrDir.find_repositoryV2'
850
response = self._call(verb, path)
851
if response[0] != b'ok':
852
raise errors.UnexpectedSmartServerResponse(response)
854
repo = self._real_bzrdir.open_repository()
855
response = response + (repo._format.network_name(),)
856
return response, repo
858
def _open_repo_v3(self, path):
859
verb = b'BzrDir.find_repositoryV3'
860
medium = self._client._medium
861
if medium._is_remote_before((1, 13)):
862
raise errors.UnknownSmartMethod(verb)
864
response = self._call(verb, path)
865
except errors.UnknownSmartMethod:
866
medium._remember_remote_is_before((1, 13))
868
if response[0] != b'ok':
869
raise errors.UnexpectedSmartServerResponse(response)
870
return response, None
872
def open_repository(self):
873
path = self._path_for_remote_call(self._client)
875
for probe in [self._open_repo_v3, self._open_repo_v2,
878
response, real_repo = probe(path)
880
except errors.UnknownSmartMethod:
883
raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
884
if response[0] != b'ok':
885
raise errors.UnexpectedSmartServerResponse(response)
886
if len(response) != 6:
887
raise SmartProtocolError(
888
'incorrect response length %s' % (response,))
889
if response[1] == b'':
890
# repo is at this dir.
891
format = response_tuple_to_repo_format(response[2:])
892
# Used to support creating a real format instance when needed.
893
format._creating_bzrdir = self
894
remote_repo = RemoteRepository(self, format)
895
format._creating_repo = remote_repo
896
if real_repo is not None:
897
remote_repo._set_real_repository(real_repo)
900
raise errors.NoRepositoryPresent(self)
902
def has_workingtree(self):
903
if self._has_working_tree is None:
904
path = self._path_for_remote_call(self._client)
906
response = self._call(b'BzrDir.has_workingtree', path)
907
except errors.UnknownSmartMethod:
909
self._has_working_tree = self._real_bzrdir.has_workingtree()
911
if response[0] not in (b'yes', b'no'):
912
raise SmartProtocolError(
913
'unexpected response code %s' % (response,))
914
self._has_working_tree = (response[0] == b'yes')
915
return self._has_working_tree
917
def open_workingtree(self, recommend_upgrade=True):
918
if self.has_workingtree():
919
raise errors.NotLocalUrl(self.root_transport)
921
raise errors.NoWorkingTree(self.root_transport.base)
923
def _path_for_remote_call(self, client):
924
"""Return the path to be used for this bzrdir in a remote call."""
925
remote_path = client.remote_path_from_transport(self.root_transport)
926
remote_path = remote_path.decode('utf-8')
927
base_url, segment_parameters = urlutils.split_segment_parameters_raw(
929
base_url = base_url.encode('utf-8')
932
def get_branch_transport(self, branch_format, name=None):
934
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
936
def get_repository_transport(self, repository_format):
938
return self._real_bzrdir.get_repository_transport(repository_format)
940
def get_workingtree_transport(self, workingtree_format):
942
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
944
def can_convert_format(self):
945
"""Upgrading of remote bzrdirs is not supported yet."""
948
def needs_format_conversion(self, format):
949
"""Upgrading of remote bzrdirs is not supported yet."""
952
def _get_config(self):
953
return RemoteBzrDirConfig(self)
955
def _get_config_store(self):
956
return RemoteControlStore(self)
959
class RemoteInventoryTree(InventoryRevisionTree):
961
def __init__(self, repository, inv, revision_id):
962
super(RemoteInventoryTree, self).__init__(repository, inv, revision_id)
964
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
965
ret = self._repository._revision_archive(
966
self.get_revision_id(), format, name, root, subdir,
967
force_mtime=force_mtime)
969
return super(RemoteInventoryTree, self).archive(
970
format, name, root, subdir, force_mtime=force_mtime)
973
def annotate_iter(self, path,
974
default_revision=_mod_revision.CURRENT_REVISION):
975
"""Return an iterator of revision_id, line tuples.
977
For working trees (and mutable trees in general), the special
978
revision_id 'current:' will be used for lines that are new in this
979
tree, e.g. uncommitted changes.
980
:param default_revision: For lines that don't match a basis, mark them
981
with this revision id. Not all implementations will make use of
984
ret = self._repository._annotate_file_revision(
985
self.get_revision_id(), path, file_id=None,
986
default_revision=default_revision)
988
return super(RemoteInventoryTree, self).annotate_iter(
989
path, default_revision=default_revision)
993
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
994
"""Format for repositories accessed over a _SmartClient.
996
Instances of this repository are represented by RemoteRepository
999
The RemoteRepositoryFormat is parameterized during construction
1000
to reflect the capabilities of the real, remote format. Specifically
1001
the attributes rich_root_data and supports_tree_reference are set
1002
on a per instance basis, and are not set (and should not be) at
1005
:ivar _custom_format: If set, a specific concrete repository format that
1006
will be used when initializing a repository with this
1007
RemoteRepositoryFormat.
1008
:ivar _creating_repo: If set, the repository object that this
1009
RemoteRepositoryFormat was created for: it can be called into
1010
to obtain data like the network name.
1013
_matchingcontroldir = RemoteBzrDirFormat()
1014
supports_full_versioned_files = True
1015
supports_leaving_lock = True
1016
supports_overriding_transport = False
1019
_mod_repository.RepositoryFormat.__init__(self)
1020
self._custom_format = None
1021
self._network_name = None
1022
self._creating_bzrdir = None
1023
self._revision_graph_can_have_wrong_parents = None
1024
self._supports_chks = None
1025
self._supports_external_lookups = None
1026
self._supports_tree_reference = None
1027
self._supports_funky_characters = None
1028
self._supports_nesting_repositories = None
1029
self._rich_root_data = None
1032
return "%s(_network_name=%r)" % (self.__class__.__name__,
1036
def fast_deltas(self):
1038
return self._custom_format.fast_deltas
1041
def rich_root_data(self):
1042
if self._rich_root_data is None:
1044
self._rich_root_data = self._custom_format.rich_root_data
1045
return self._rich_root_data
1048
def supports_chks(self):
1049
if self._supports_chks is None:
1051
self._supports_chks = self._custom_format.supports_chks
1052
return self._supports_chks
1055
def supports_external_lookups(self):
1056
if self._supports_external_lookups is None:
1058
self._supports_external_lookups = \
1059
self._custom_format.supports_external_lookups
1060
return self._supports_external_lookups
1063
def supports_funky_characters(self):
1064
if self._supports_funky_characters is None:
1066
self._supports_funky_characters = \
1067
self._custom_format.supports_funky_characters
1068
return self._supports_funky_characters
1071
def supports_nesting_repositories(self):
1072
if self._supports_nesting_repositories is None:
1074
self._supports_nesting_repositories = \
1075
self._custom_format.supports_nesting_repositories
1076
return self._supports_nesting_repositories
1079
def supports_tree_reference(self):
1080
if self._supports_tree_reference is None:
1082
self._supports_tree_reference = \
1083
self._custom_format.supports_tree_reference
1084
return self._supports_tree_reference
1087
def revision_graph_can_have_wrong_parents(self):
1088
if self._revision_graph_can_have_wrong_parents is None:
1090
self._revision_graph_can_have_wrong_parents = \
1091
self._custom_format.revision_graph_can_have_wrong_parents
1092
return self._revision_graph_can_have_wrong_parents
1094
def _vfs_initialize(self, a_controldir, shared):
1095
"""Helper for common code in initialize."""
1096
if self._custom_format:
1097
# Custom format requested
1098
result = self._custom_format.initialize(
1099
a_controldir, shared=shared)
1100
elif self._creating_bzrdir is not None:
1101
# Use the format that the repository we were created to back
1103
prior_repo = self._creating_bzrdir.open_repository()
1104
prior_repo._ensure_real()
1105
result = prior_repo._real_repository._format.initialize(
1106
a_controldir, shared=shared)
1108
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1109
# support remote initialization.
1110
# We delegate to a real object at this point (as RemoteBzrDir
1111
# delegate to the repository format which would lead to infinite
1112
# recursion if we just called a_controldir.create_repository.
1113
a_controldir._ensure_real()
1114
result = a_controldir._real_bzrdir.create_repository(shared=shared)
1115
if not isinstance(result, RemoteRepository):
1116
return self.open(a_controldir)
1120
def initialize(self, a_controldir, shared=False):
1121
# Being asked to create on a non RemoteBzrDir:
1122
if not isinstance(a_controldir, RemoteBzrDir):
1123
return self._vfs_initialize(a_controldir, shared)
1124
medium = a_controldir._client._medium
1125
if medium._is_remote_before((1, 13)):
1126
return self._vfs_initialize(a_controldir, shared)
1127
# Creating on a remote bzr dir.
1128
# 1) get the network name to use.
1129
if self._custom_format:
1130
network_name = self._custom_format.network_name()
1131
elif self._network_name:
1132
network_name = self._network_name
1134
# Select the current breezy default and ask for that.
1135
reference_bzrdir_format = controldir.format_registry.get(
1137
reference_format = reference_bzrdir_format.repository_format
1138
network_name = reference_format.network_name()
1139
# 2) try direct creation via RPC
1140
path = a_controldir._path_for_remote_call(a_controldir._client)
1141
verb = b'BzrDir.create_repository'
1143
shared_str = b'True'
1145
shared_str = b'False'
1147
response = a_controldir._call(verb, path, network_name, shared_str)
1148
except errors.UnknownSmartMethod:
1149
# Fallback - use vfs methods
1150
medium._remember_remote_is_before((1, 13))
1151
return self._vfs_initialize(a_controldir, shared)
1153
# Turn the response into a RemoteRepository object.
1154
format = response_tuple_to_repo_format(response[1:])
1155
# Used to support creating a real format instance when needed.
1156
format._creating_bzrdir = a_controldir
1157
remote_repo = RemoteRepository(a_controldir, format)
1158
format._creating_repo = remote_repo
1161
def open(self, a_controldir):
1162
if not isinstance(a_controldir, RemoteBzrDir):
1163
raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
1164
return a_controldir.open_repository()
1166
def _ensure_real(self):
1167
if self._custom_format is None:
1169
self._custom_format = _mod_repository.network_format_registry.get(
1172
raise errors.UnknownFormatError(kind='repository',
1173
format=self._network_name)
1176
def _fetch_order(self):
1178
return self._custom_format._fetch_order
1181
def _fetch_uses_deltas(self):
1183
return self._custom_format._fetch_uses_deltas
1186
def _fetch_reconcile(self):
1188
return self._custom_format._fetch_reconcile
1190
def get_format_description(self):
1192
return 'Remote: ' + self._custom_format.get_format_description()
1194
def __eq__(self, other):
1195
return self.__class__ is other.__class__
1197
def network_name(self):
1198
if self._network_name:
1199
return self._network_name
1200
self._creating_repo._ensure_real()
1201
return self._creating_repo._real_repository._format.network_name()
1204
def pack_compresses(self):
1206
return self._custom_format.pack_compresses
1209
def _serializer(self):
1211
return self._custom_format._serializer
1214
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1215
lock._RelockDebugMixin):
1216
"""Repository accessed over rpc.
1218
For the moment most operations are performed using local transport-backed
1222
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1223
"""Create a RemoteRepository instance.
1225
:param remote_bzrdir: The bzrdir hosting this repository.
1226
:param format: The RemoteFormat object to use.
1227
:param real_repository: If not None, a local implementation of the
1228
repository logic for the repository, usually accessing the data
1230
:param _client: Private testing parameter - override the smart client
1231
to be used by the repository.
1234
self._real_repository = real_repository
1236
self._real_repository = None
1237
self.controldir = remote_bzrdir
1239
self._client = remote_bzrdir._client
1241
self._client = _client
1242
self._format = format
1243
self._lock_mode = None
1244
self._lock_token = None
1245
self._write_group_tokens = None
1246
self._lock_count = 0
1247
self._leave_lock = False
1248
# Cache of revision parents; misses are cached during read locks, and
1249
# write locks when no _real_repository has been set.
1250
self._unstacked_provider = graph.CachingParentsProvider(
1251
get_parent_map=self._get_parent_map_rpc)
1252
self._unstacked_provider.disable_cache()
1254
# These depend on the actual remote format, so force them off for
1255
# maximum compatibility. XXX: In future these should depend on the
1256
# remote repository instance, but this is irrelevant until we perform
1257
# reconcile via an RPC call.
1258
self._reconcile_does_inventory_gc = False
1259
self._reconcile_fixes_text_parents = False
1260
self._reconcile_backsup_inventory = False
1261
self.base = self.controldir.transport.base
1262
# Additional places to query for data.
1263
self._fallback_repositories = []
1266
def user_transport(self):
1267
return self.controldir.user_transport
1270
def control_transport(self):
1271
# XXX: Normally you shouldn't directly get at the remote repository
1272
# transport, but I'm not sure it's worth making this method
1273
# optional -- mbp 2010-04-21
1274
return self.controldir.get_repository_transport(None)
1277
return "%s(%s)" % (self.__class__.__name__, self.base)
1281
def abort_write_group(self, suppress_errors=False):
1282
"""Complete a write group on the decorated repository.
1284
Smart methods perform operations in a single step so this API
1285
is not really applicable except as a compatibility thunk
1286
for older plugins that don't use e.g. the CommitBuilder
1289
:param suppress_errors: see Repository.abort_write_group.
1291
if self._real_repository:
1293
return self._real_repository.abort_write_group(
1294
suppress_errors=suppress_errors)
1295
if not self.is_in_write_group():
1297
mutter('(suppressed) not in write group')
1299
raise errors.BzrError("not in write group")
1300
path = self.controldir._path_for_remote_call(self._client)
1302
response = self._call(b'Repository.abort_write_group', path,
1304
[token.encode('utf-8') for token in self._write_group_tokens])
1305
except Exception as exc:
1306
self._write_group = None
1307
if not suppress_errors:
1309
mutter('abort_write_group failed')
1310
log_exception_quietly()
1311
note(gettext('bzr: ERROR (ignored): %s'), exc)
1313
if response != (b'ok', ):
1314
raise errors.UnexpectedSmartServerResponse(response)
1315
self._write_group_tokens = None
1318
def chk_bytes(self):
1319
"""Decorate the real repository for now.
1321
In the long term a full blown network facility is needed to avoid
1322
creating a real repository object locally.
1325
return self._real_repository.chk_bytes
1327
def commit_write_group(self):
1328
"""Complete a write group on the decorated repository.
1330
Smart methods perform operations in a single step so this API
1331
is not really applicable except as a compatibility thunk
1332
for older plugins that don't use e.g. the CommitBuilder
1335
if self._real_repository:
1337
return self._real_repository.commit_write_group()
1338
if not self.is_in_write_group():
1339
raise errors.BzrError("not in write group")
1340
path = self.controldir._path_for_remote_call(self._client)
1341
response = self._call(b'Repository.commit_write_group', path,
1342
self._lock_token, [token.encode('utf-8') for token in self._write_group_tokens])
1343
if response != (b'ok', ):
1344
raise errors.UnexpectedSmartServerResponse(response)
1345
self._write_group_tokens = None
1346
# Refresh data after writing to the repository.
1349
def resume_write_group(self, tokens):
1350
if self._real_repository:
1351
return self._real_repository.resume_write_group(tokens)
1352
path = self.controldir._path_for_remote_call(self._client)
1354
response = self._call(b'Repository.check_write_group', path,
1355
self._lock_token, [token.encode('utf-8') for token in tokens])
1356
except errors.UnknownSmartMethod:
1358
return self._real_repository.resume_write_group(tokens)
1359
if response != (b'ok', ):
1360
raise errors.UnexpectedSmartServerResponse(response)
1361
self._write_group_tokens = tokens
1363
def suspend_write_group(self):
1364
if self._real_repository:
1365
return self._real_repository.suspend_write_group()
1366
ret = self._write_group_tokens or []
1367
self._write_group_tokens = None
1370
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1372
return self._real_repository.get_missing_parent_inventories(
1373
check_for_missing_texts=check_for_missing_texts)
1375
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1377
return self._real_repository.get_rev_id_for_revno(
1380
def get_rev_id_for_revno(self, revno, known_pair):
1381
"""See Repository.get_rev_id_for_revno."""
1382
path = self.controldir._path_for_remote_call(self._client)
1384
if self._client._medium._is_remote_before((1, 17)):
1385
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1386
response = self._call(
1387
b'Repository.get_rev_id_for_revno', path, revno, known_pair)
1388
except errors.UnknownSmartMethod:
1389
self._client._medium._remember_remote_is_before((1, 17))
1390
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1391
except errors.UnknownErrorFromSmartServer as e:
1392
# Older versions of Bazaar/Breezy (<< 3.0.0) would raise a
1393
# ValueError instead of returning revno-outofbounds
1394
if len(e.error_tuple) < 3:
1396
if e.error_tuple[:2] != (b'error', b'ValueError'):
1399
br"requested revno \(([0-9]+)\) is later than given "
1400
br"known revno \(([0-9]+)\)", e.error_tuple[2])
1403
raise errors.RevnoOutOfBounds(
1404
int(m.group(1)), (0, int(m.group(2))))
1405
if response[0] == b'ok':
1406
return True, response[1]
1407
elif response[0] == b'history-incomplete':
1408
known_pair = response[1:3]
1409
for fallback in self._fallback_repositories:
1410
found, result = fallback.get_rev_id_for_revno(
1416
# Not found in any fallbacks
1417
return False, known_pair
1419
raise errors.UnexpectedSmartServerResponse(response)
1421
def _ensure_real(self):
1422
"""Ensure that there is a _real_repository set.
1424
Used before calls to self._real_repository.
1426
Note that _ensure_real causes many roundtrips to the server which are
1427
not desirable, and prevents the use of smart one-roundtrip RPC's to
1428
perform complex operations (such as accessing parent data, streaming
1429
revisions etc). Adding calls to _ensure_real should only be done when
1430
bringing up new functionality, adding fallbacks for smart methods that
1431
require a fallback path, and never to replace an existing smart method
1432
invocation. If in doubt chat to the bzr network team.
1434
if self._real_repository is None:
1435
if 'hpssvfs' in debug.debug_flags:
1437
warning('VFS Repository access triggered\n%s',
1438
''.join(traceback.format_stack()))
1439
self._unstacked_provider.missing_keys.clear()
1440
self.controldir._ensure_real()
1441
self._set_real_repository(
1442
self.controldir._real_bzrdir.open_repository())
1444
def _translate_error(self, err, **context):
1445
self.controldir._translate_error(err, repository=self, **context)
1447
def find_text_key_references(self):
1448
"""Find the text key references within the repository.
1450
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1451
to whether they were referred to by the inventory of the
1452
revision_id that they contain. The inventory texts from all present
1453
revision ids are assessed to generate this report.
1456
return self._real_repository.find_text_key_references()
1458
def _generate_text_key_index(self):
1459
"""Generate a new text key index for the repository.
1461
This is an expensive function that will take considerable time to run.
1463
:return: A dict mapping (file_id, revision_id) tuples to a list of
1464
parents, also (file_id, revision_id) tuples.
1467
return self._real_repository._generate_text_key_index()
1469
def _get_revision_graph(self, revision_id):
1470
"""Private method for using with old (< 1.2) servers to fallback."""
1471
if revision_id is None:
1473
elif _mod_revision.is_null(revision_id):
1476
path = self.controldir._path_for_remote_call(self._client)
1477
response = self._call_expecting_body(
1478
b'Repository.get_revision_graph', path, revision_id)
1479
response_tuple, response_handler = response
1480
if response_tuple[0] != b'ok':
1481
raise errors.UnexpectedSmartServerResponse(response_tuple)
1482
coded = response_handler.read_body_bytes()
1484
# no revisions in this repository!
1486
lines = coded.split(b'\n')
1489
d = tuple(line.split())
1490
revision_graph[d[0]] = d[1:]
1492
return revision_graph
1494
def _get_sink(self):
1495
"""See Repository._get_sink()."""
1496
return RemoteStreamSink(self)
1498
def _get_source(self, to_format):
1499
"""Return a source for streaming from this repository."""
1500
return RemoteStreamSource(self, to_format)
1502
def get_file_graph(self):
1503
with self.lock_read():
1504
return graph.Graph(self.texts)
1506
def has_revision(self, revision_id):
1507
"""True if this repository has a copy of the revision."""
1508
# Copy of breezy.repository.Repository.has_revision
1509
with self.lock_read():
1510
return revision_id in self.has_revisions((revision_id,))
1512
def has_revisions(self, revision_ids):
1513
"""Probe to find out the presence of multiple revisions.
1515
:param revision_ids: An iterable of revision_ids.
1516
:return: A set of the revision_ids that were present.
1518
with self.lock_read():
1519
# Copy of breezy.repository.Repository.has_revisions
1520
parent_map = self.get_parent_map(revision_ids)
1521
result = set(parent_map)
1522
if _mod_revision.NULL_REVISION in revision_ids:
1523
result.add(_mod_revision.NULL_REVISION)
1526
def _has_same_fallbacks(self, other_repo):
1527
"""Returns true if the repositories have the same fallbacks."""
1528
# XXX: copied from Repository; it should be unified into a base class
1529
# <https://bugs.launchpad.net/bzr/+bug/401622>
1530
my_fb = self._fallback_repositories
1531
other_fb = other_repo._fallback_repositories
1532
if len(my_fb) != len(other_fb):
1534
for f, g in zip(my_fb, other_fb):
1535
if not f.has_same_location(g):
1539
def has_same_location(self, other):
1540
# TODO: Move to RepositoryBase and unify with the regular Repository
1541
# one; unfortunately the tests rely on slightly different behaviour at
1542
# present -- mbp 20090710
1543
return (self.__class__ is other.__class__
1544
and self.controldir.transport.base == other.controldir.transport.base)
1546
def get_graph(self, other_repository=None):
1547
"""Return the graph for this repository format"""
1548
parents_provider = self._make_parents_provider(other_repository)
1549
return graph.Graph(parents_provider)
1551
def get_known_graph_ancestry(self, revision_ids):
1552
"""Return the known graph for a set of revision ids and their ancestors.
1554
with self.lock_read():
1555
revision_graph = dict(((key, value) for key, value in
1556
self.get_graph().iter_ancestry(revision_ids) if value is not None))
1557
revision_graph = _mod_repository._strip_NULL_ghosts(revision_graph)
1558
return graph.KnownGraph(revision_graph)
1560
def gather_stats(self, revid=None, committers=None):
1561
"""See Repository.gather_stats()."""
1562
path = self.controldir._path_for_remote_call(self._client)
1563
# revid can be None to indicate no revisions, not just NULL_REVISION
1564
if revid is None or _mod_revision.is_null(revid):
1568
if committers is None or not committers:
1569
fmt_committers = b'no'
1571
fmt_committers = b'yes'
1572
response_tuple, response_handler = self._call_expecting_body(
1573
b'Repository.gather_stats', path, fmt_revid, fmt_committers)
1574
if response_tuple[0] != b'ok':
1575
raise errors.UnexpectedSmartServerResponse(response_tuple)
1577
body = response_handler.read_body_bytes()
1579
for line in body.split(b'\n'):
1582
key, val_text = line.split(b':')
1583
key = key.decode('ascii')
1584
if key in ('revisions', 'size', 'committers'):
1585
result[key] = int(val_text)
1586
elif key in ('firstrev', 'latestrev'):
1587
values = val_text.split(b' ')[1:]
1588
result[key] = (float(values[0]), int(values[1]))
1592
def find_branches(self, using=False):
1593
"""See Repository.find_branches()."""
1594
# should be an API call to the server.
1596
return self._real_repository.find_branches(using=using)
1598
def get_physical_lock_status(self):
1599
"""See Repository.get_physical_lock_status()."""
1600
path = self.controldir._path_for_remote_call(self._client)
1602
response = self._call(b'Repository.get_physical_lock_status', path)
1603
except errors.UnknownSmartMethod:
1605
return self._real_repository.get_physical_lock_status()
1606
if response[0] not in (b'yes', b'no'):
1607
raise errors.UnexpectedSmartServerResponse(response)
1608
return (response[0] == b'yes')
1610
def is_in_write_group(self):
1611
"""Return True if there is an open write group.
1613
write groups are only applicable locally for the smart server..
1615
if self._write_group_tokens is not None:
1617
if self._real_repository:
1618
return self._real_repository.is_in_write_group()
1620
def is_locked(self):
1621
return self._lock_count >= 1
1623
def is_shared(self):
1624
"""See Repository.is_shared()."""
1625
path = self.controldir._path_for_remote_call(self._client)
1626
response = self._call(b'Repository.is_shared', path)
1627
if response[0] not in (b'yes', b'no'):
1628
raise SmartProtocolError(
1629
'unexpected response code %s' % (response,))
1630
return response[0] == b'yes'
1632
def is_write_locked(self):
1633
return self._lock_mode == 'w'
1635
def _warn_if_deprecated(self, branch=None):
1636
# If we have a real repository, the check will be done there, if we
1637
# don't the check will be done remotely.
1640
def lock_read(self):
1641
"""Lock the repository for read operations.
1643
:return: A breezy.lock.LogicalLockResult.
1645
# wrong eventually - want a local lock cache context
1646
if not self._lock_mode:
1647
self._note_lock('r')
1648
self._lock_mode = 'r'
1649
self._lock_count = 1
1650
self._unstacked_provider.enable_cache(cache_misses=True)
1651
if self._real_repository is not None:
1652
self._real_repository.lock_read()
1653
for repo in self._fallback_repositories:
1656
self._lock_count += 1
1657
return lock.LogicalLockResult(self.unlock)
1659
def _remote_lock_write(self, token):
1660
path = self.controldir._path_for_remote_call(self._client)
1663
err_context = {'token': token}
1664
response = self._call(b'Repository.lock_write', path, token,
1666
if response[0] == b'ok':
1667
ok, token = response
1670
raise errors.UnexpectedSmartServerResponse(response)
1672
def lock_write(self, token=None, _skip_rpc=False):
1673
if not self._lock_mode:
1674
self._note_lock('w')
1676
if self._lock_token is not None:
1677
if token != self._lock_token:
1678
raise errors.TokenMismatch(token, self._lock_token)
1679
self._lock_token = token
1681
self._lock_token = self._remote_lock_write(token)
1682
# if self._lock_token is None, then this is something like packs or
1683
# svn where we don't get to lock the repo, or a weave style repository
1684
# where we cannot lock it over the wire and attempts to do so will
1686
if self._real_repository is not None:
1687
self._real_repository.lock_write(token=self._lock_token)
1688
if token is not None:
1689
self._leave_lock = True
1691
self._leave_lock = False
1692
self._lock_mode = 'w'
1693
self._lock_count = 1
1694
cache_misses = self._real_repository is None
1695
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1696
for repo in self._fallback_repositories:
1697
# Writes don't affect fallback repos
1699
elif self._lock_mode == 'r':
1700
raise errors.ReadOnlyError(self)
1702
self._lock_count += 1
1703
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1705
def leave_lock_in_place(self):
1706
if not self._lock_token:
1707
raise NotImplementedError(self.leave_lock_in_place)
1708
self._leave_lock = True
1710
def dont_leave_lock_in_place(self):
1711
if not self._lock_token:
1712
raise NotImplementedError(self.dont_leave_lock_in_place)
1713
self._leave_lock = False
1715
def _set_real_repository(self, repository):
1716
"""Set the _real_repository for this repository.
1718
:param repository: The repository to fallback to for non-hpss
1719
implemented operations.
1721
if self._real_repository is not None:
1722
# Replacing an already set real repository.
1723
# We cannot do this [currently] if the repository is locked -
1724
# synchronised state might be lost.
1725
if self.is_locked():
1726
raise AssertionError('_real_repository is already set')
1727
if isinstance(repository, RemoteRepository):
1728
raise AssertionError()
1729
self._real_repository = repository
1730
# three code paths happen here:
1731
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1732
# up stacking. In this case self._fallback_repositories is [], and the
1733
# real repo is already setup. Preserve the real repo and
1734
# RemoteRepository.add_fallback_repository will avoid adding
1736
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1737
# ensure_real is triggered from a branch, the real repository to
1738
# set already has a matching list with separate instances, but
1739
# as they are also RemoteRepositories we don't worry about making the
1740
# lists be identical.
1741
# 3) new servers, RemoteRepository.ensure_real is triggered before
1742
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1743
# and need to populate it.
1744
if (self._fallback_repositories
1745
and len(self._real_repository._fallback_repositories)
1746
!= len(self._fallback_repositories)):
1747
if len(self._real_repository._fallback_repositories):
1748
raise AssertionError(
1749
"cannot cleanly remove existing _fallback_repositories")
1750
for fb in self._fallback_repositories:
1751
self._real_repository.add_fallback_repository(fb)
1752
if self._lock_mode == 'w':
1753
# if we are already locked, the real repository must be able to
1754
# acquire the lock with our token.
1755
self._real_repository.lock_write(self._lock_token)
1756
elif self._lock_mode == 'r':
1757
self._real_repository.lock_read()
1758
if self._write_group_tokens is not None:
1759
# if we are already in a write group, resume it
1760
self._real_repository.resume_write_group(self._write_group_tokens)
1761
self._write_group_tokens = None
1763
def start_write_group(self):
1764
"""Start a write group on the decorated repository.
1766
Smart methods perform operations in a single step so this API
1767
is not really applicable except as a compatibility thunk
1768
for older plugins that don't use e.g. the CommitBuilder
1771
if self._real_repository:
1773
return self._real_repository.start_write_group()
1774
if not self.is_write_locked():
1775
raise errors.NotWriteLocked(self)
1776
if self._write_group_tokens is not None:
1777
raise errors.BzrError('already in a write group')
1778
path = self.controldir._path_for_remote_call(self._client)
1780
response = self._call(b'Repository.start_write_group', path,
1782
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1784
return self._real_repository.start_write_group()
1785
if response[0] != b'ok':
1786
raise errors.UnexpectedSmartServerResponse(response)
1787
self._write_group_tokens = [
1788
token.decode('utf-8') for token in response[1]]
1790
def _unlock(self, token):
1791
path = self.controldir._path_for_remote_call(self._client)
1793
# with no token the remote repository is not persistently locked.
1795
err_context = {'token': token}
1796
response = self._call(b'Repository.unlock', path, token,
1798
if response == (b'ok',):
1801
raise errors.UnexpectedSmartServerResponse(response)
1803
@only_raises(errors.LockNotHeld, errors.LockBroken)
1805
if not self._lock_count:
1806
return lock.cant_unlock_not_held(self)
1807
self._lock_count -= 1
1808
if self._lock_count > 0:
1810
self._unstacked_provider.disable_cache()
1811
old_mode = self._lock_mode
1812
self._lock_mode = None
1814
# The real repository is responsible at present for raising an
1815
# exception if it's in an unfinished write group. However, it
1816
# normally will *not* actually remove the lock from disk - that's
1817
# done by the server on receiving the Repository.unlock call.
1818
# This is just to let the _real_repository stay up to date.
1819
if self._real_repository is not None:
1820
self._real_repository.unlock()
1821
elif self._write_group_tokens is not None:
1822
self.abort_write_group()
1824
# The rpc-level lock should be released even if there was a
1825
# problem releasing the vfs-based lock.
1827
# Only write-locked repositories need to make a remote method
1828
# call to perform the unlock.
1829
old_token = self._lock_token
1830
self._lock_token = None
1831
if not self._leave_lock:
1832
self._unlock(old_token)
1833
# Fallbacks are always 'lock_read()' so we don't pay attention to
1835
for repo in self._fallback_repositories:
1838
def break_lock(self):
1839
# should hand off to the network
1840
path = self.controldir._path_for_remote_call(self._client)
1842
response = self._call(b"Repository.break_lock", path)
1843
except errors.UnknownSmartMethod:
1845
return self._real_repository.break_lock()
1846
if response != (b'ok',):
1847
raise errors.UnexpectedSmartServerResponse(response)
1849
def _get_tarball(self, compression):
1850
"""Return a TemporaryFile containing a repository tarball.
1852
Returns None if the server does not support sending tarballs.
1855
path = self.controldir._path_for_remote_call(self._client)
1857
response, protocol = self._call_expecting_body(
1858
b'Repository.tarball', path, compression.encode('ascii'))
1859
except errors.UnknownSmartMethod:
1860
protocol.cancel_read_body()
1862
if response[0] == b'ok':
1863
# Extract the tarball and return it
1864
t = tempfile.NamedTemporaryFile()
1865
# TODO: rpc layer should read directly into it...
1866
t.write(protocol.read_body_bytes())
1869
raise errors.UnexpectedSmartServerResponse(response)
1871
def sprout(self, to_bzrdir, revision_id=None):
1872
"""Create a descendent repository for new development.
1874
Unlike clone, this does not copy the settings of the repository.
1876
with self.lock_read():
1877
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1878
dest_repo.fetch(self, revision_id=revision_id)
1881
def _create_sprouting_repo(self, a_controldir, shared):
1882
if not isinstance(a_controldir._format, self.controldir._format.__class__):
1883
# use target default format.
1884
dest_repo = a_controldir.create_repository()
1886
# Most control formats need the repository to be specifically
1887
# created, but on some old all-in-one formats it's not needed
1889
dest_repo = self._format.initialize(
1890
a_controldir, shared=shared)
1891
except errors.UninitializableFormat:
1892
dest_repo = a_controldir.open_repository()
1895
# These methods are just thin shims to the VFS object for now.
1897
def revision_tree(self, revision_id):
1898
with self.lock_read():
1899
revision_id = _mod_revision.ensure_null(revision_id)
1900
if revision_id == _mod_revision.NULL_REVISION:
1901
return InventoryRevisionTree(self,
1902
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1904
return list(self.revision_trees([revision_id]))[0]
1906
def get_serializer_format(self):
1907
path = self.controldir._path_for_remote_call(self._client)
1909
response = self._call(b'VersionedFileRepository.get_serializer_format',
1911
except errors.UnknownSmartMethod:
1913
return self._real_repository.get_serializer_format()
1914
if response[0] != b'ok':
1915
raise errors.UnexpectedSmartServerResponse(response)
1918
def get_commit_builder(self, branch, parents, config, timestamp=None,
1919
timezone=None, committer=None, revprops=None,
1920
revision_id=None, lossy=False):
1921
"""Obtain a CommitBuilder for this repository.
1923
:param branch: Branch to commit to.
1924
:param parents: Revision ids of the parents of the new revision.
1925
:param config: Configuration to use.
1926
:param timestamp: Optional timestamp recorded for commit.
1927
:param timezone: Optional timezone for timestamp.
1928
:param committer: Optional committer to set for commit.
1929
:param revprops: Optional dictionary of revision properties.
1930
:param revision_id: Optional revision id.
1931
:param lossy: Whether to discard data that can not be natively
1932
represented, when pushing to a foreign VCS
1934
if self._fallback_repositories and not self._format.supports_chks:
1935
raise errors.BzrError("Cannot commit directly to a stacked branch"
1936
" in pre-2a formats. See "
1937
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1938
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1939
result = commit_builder_kls(self, parents, config,
1940
timestamp, timezone, committer, revprops, revision_id,
1942
self.start_write_group()
1945
def add_fallback_repository(self, repository):
1946
"""Add a repository to use for looking up data not held locally.
1948
:param repository: A repository.
1950
if not self._format.supports_external_lookups:
1951
raise errors.UnstackableRepositoryFormat(
1952
self._format.network_name(), self.base)
1953
# We need to accumulate additional repositories here, to pass them in
1956
# Make the check before we lock: this raises an exception.
1957
self._check_fallback_repository(repository)
1958
if self.is_locked():
1959
# We will call fallback.unlock() when we transition to the unlocked
1960
# state, so always add a lock here. If a caller passes us a locked
1961
# repository, they are responsible for unlocking it later.
1962
repository.lock_read()
1963
self._fallback_repositories.append(repository)
1964
# If self._real_repository was parameterised already (e.g. because a
1965
# _real_branch had its get_stacked_on_url method called), then the
1966
# repository to be added may already be in the _real_repositories list.
1967
if self._real_repository is not None:
1968
fallback_locations = [repo.user_url for repo in
1969
self._real_repository._fallback_repositories]
1970
if repository.user_url not in fallback_locations:
1971
self._real_repository.add_fallback_repository(repository)
1973
def _check_fallback_repository(self, repository):
1974
"""Check that this repository can fallback to repository safely.
1976
Raise an error if not.
1978
:param repository: A repository to fallback to.
1980
return _mod_repository.InterRepository._assert_same_model(
1983
def add_inventory(self, revid, inv, parents):
1985
return self._real_repository.add_inventory(revid, inv, parents)
1987
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1988
parents, basis_inv=None, propagate_caches=False):
1990
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1991
delta, new_revision_id, parents, basis_inv=basis_inv,
1992
propagate_caches=propagate_caches)
1994
def add_revision(self, revision_id, rev, inv=None):
1995
_mod_revision.check_not_reserved_id(revision_id)
1996
key = (revision_id,)
1997
# check inventory present
1998
if not self.inventories.get_parent_map([key]):
2000
raise errors.WeaveRevisionNotPresent(revision_id,
2003
# yes, this is not suitable for adding with ghosts.
2004
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
2007
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
2008
self._add_revision(rev)
2010
def _add_revision(self, rev):
2011
if self._real_repository is not None:
2012
return self._real_repository._add_revision(rev)
2013
lines = self._serializer.write_revision_to_lines(rev)
2014
key = (rev.revision_id,)
2015
parents = tuple((parent,) for parent in rev.parent_ids)
2016
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
2017
[('revisions', [ChunkedContentFactory(key, parents, None, lines, chunks_are_lines=True)])],
2018
self._format, self._write_group_tokens)
2020
def get_inventory(self, revision_id):
2021
with self.lock_read():
2022
return list(self.iter_inventories([revision_id]))[0]
2024
def _iter_inventories_rpc(self, revision_ids, ordering):
2025
if ordering is None:
2026
ordering = 'unordered'
2027
path = self.controldir._path_for_remote_call(self._client)
2028
body = b"\n".join(revision_ids)
2029
response_tuple, response_handler = (
2030
self._call_with_body_bytes_expecting_body(
2031
b"VersionedFileRepository.get_inventories",
2032
(path, ordering.encode('ascii')), body))
2033
if response_tuple[0] != b"ok":
2034
raise errors.UnexpectedSmartServerResponse(response_tuple)
2035
deserializer = inventory_delta.InventoryDeltaDeserializer()
2036
byte_stream = response_handler.read_streamed_body()
2037
decoded = smart_repo._byte_stream_to_stream(byte_stream)
2039
# no results whatsoever
2041
src_format, stream = decoded
2042
if src_format.network_name() != self._format.network_name():
2043
raise AssertionError(
2044
"Mismatched RemoteRepository and stream src %r, %r" % (
2045
src_format.network_name(), self._format.network_name()))
2046
# ignore the src format, it's not really relevant
2047
prev_inv = Inventory(root_id=None,
2048
revision_id=_mod_revision.NULL_REVISION)
2049
# there should be just one substream, with inventory deltas
2051
substream_kind, substream = next(stream)
2052
except StopIteration:
2054
if substream_kind != "inventory-deltas":
2055
raise AssertionError(
2056
"Unexpected stream %r received" % substream_kind)
2057
for record in substream:
2058
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
2059
deserializer.parse_text_bytes(record.get_bytes_as("lines")))
2060
if parent_id != prev_inv.revision_id:
2061
raise AssertionError("invalid base %r != %r" % (parent_id,
2062
prev_inv.revision_id))
2063
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
2064
yield inv, inv.revision_id
2067
def _iter_inventories_vfs(self, revision_ids, ordering=None):
2069
return self._real_repository._iter_inventories(revision_ids, ordering)
2071
def iter_inventories(self, revision_ids, ordering=None):
2072
"""Get many inventories by revision_ids.
2074
This will buffer some or all of the texts used in constructing the
2075
inventories in memory, but will only parse a single inventory at a
2078
:param revision_ids: The expected revision ids of the inventories.
2079
:param ordering: optional ordering, e.g. 'topological'. If not
2080
specified, the order of revision_ids will be preserved (by
2081
buffering if necessary).
2082
:return: An iterator of inventories.
2084
if ((None in revision_ids) or
2085
(_mod_revision.NULL_REVISION in revision_ids)):
2086
raise ValueError('cannot get null revision inventory')
2087
for inv, revid in self._iter_inventories(revision_ids, ordering):
2089
raise errors.NoSuchRevision(self, revid)
2092
def _iter_inventories(self, revision_ids, ordering=None):
2093
if len(revision_ids) == 0:
2095
missing = set(revision_ids)
2096
if ordering is None:
2097
order_as_requested = True
2099
order = list(revision_ids)
2101
next_revid = order.pop()
2103
order_as_requested = False
2104
if ordering != 'unordered' and self._fallback_repositories:
2105
raise ValueError('unsupported ordering %r' % ordering)
2106
iter_inv_fns = [self._iter_inventories_rpc] + [
2107
fallback._iter_inventories for fallback in
2108
self._fallback_repositories]
2110
for iter_inv in iter_inv_fns:
2111
request = [revid for revid in revision_ids if revid in missing]
2112
for inv, revid in iter_inv(request, ordering):
2115
missing.remove(inv.revision_id)
2116
if ordering != 'unordered':
2120
if order_as_requested:
2121
# Yield as many results as we can while preserving order.
2122
while next_revid in invs:
2123
inv = invs.pop(next_revid)
2124
yield inv, inv.revision_id
2126
next_revid = order.pop()
2128
# We still want to fully consume the stream, just
2129
# in case it is not actually finished at this point
2132
except errors.UnknownSmartMethod:
2133
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2137
if order_as_requested:
2138
if next_revid is not None:
2139
yield None, next_revid
2142
yield invs.get(revid), revid
2145
yield None, missing.pop()
2147
def get_revision(self, revision_id):
2148
with self.lock_read():
2149
return self.get_revisions([revision_id])[0]
2151
def get_transaction(self):
2153
return self._real_repository.get_transaction()
2155
def clone(self, a_controldir, revision_id=None):
2156
with self.lock_read():
2157
dest_repo = self._create_sprouting_repo(
2158
a_controldir, shared=self.is_shared())
2159
self.copy_content_into(dest_repo, revision_id)
2162
def make_working_trees(self):
2163
"""See Repository.make_working_trees"""
2164
path = self.controldir._path_for_remote_call(self._client)
2166
response = self._call(b'Repository.make_working_trees', path)
2167
except errors.UnknownSmartMethod:
2169
return self._real_repository.make_working_trees()
2170
if response[0] not in (b'yes', b'no'):
2171
raise SmartProtocolError(
2172
'unexpected response code %s' % (response,))
2173
return response[0] == b'yes'
2175
def refresh_data(self):
2176
"""Re-read any data needed to synchronise with disk.
2178
This method is intended to be called after another repository instance
2179
(such as one used by a smart server) has inserted data into the
2180
repository. On all repositories this will work outside of write groups.
2181
Some repository formats (pack and newer for breezy native formats)
2182
support refresh_data inside write groups. If called inside a write
2183
group on a repository that does not support refreshing in a write group
2184
IsInWriteGroupError will be raised.
2186
if self._real_repository is not None:
2187
self._real_repository.refresh_data()
2188
# Refresh the parents cache for this object
2189
self._unstacked_provider.disable_cache()
2190
self._unstacked_provider.enable_cache()
2192
def revision_ids_to_search_result(self, result_set):
2193
"""Convert a set of revision ids to a graph SearchResult."""
2194
result_parents = set()
2195
for parents in self.get_graph().get_parent_map(result_set).values():
2196
result_parents.update(parents)
2197
included_keys = result_set.intersection(result_parents)
2198
start_keys = result_set.difference(included_keys)
2199
exclude_keys = result_parents.difference(result_set)
2200
result = vf_search.SearchResult(start_keys, exclude_keys,
2201
len(result_set), result_set)
2204
def search_missing_revision_ids(self, other,
2205
find_ghosts=True, revision_ids=None, if_present_ids=None,
2207
"""Return the revision ids that other has that this does not.
2209
These are returned in topological order.
2211
revision_id: only return revision ids included by revision_id.
2213
with self.lock_read():
2214
inter_repo = _mod_repository.InterRepository.get(other, self)
2215
return inter_repo.search_missing_revision_ids(
2216
find_ghosts=find_ghosts, revision_ids=revision_ids,
2217
if_present_ids=if_present_ids, limit=limit)
2219
def fetch(self, source, revision_id=None, find_ghosts=False,
2220
fetch_spec=None, lossy=False):
2221
# No base implementation to use as RemoteRepository is not a subclass
2222
# of Repository; so this is a copy of Repository.fetch().
2223
if fetch_spec is not None and revision_id is not None:
2224
raise AssertionError(
2225
"fetch_spec and revision_id are mutually exclusive.")
2226
if self.is_in_write_group():
2227
raise errors.InternalBzrError(
2228
"May not fetch while in a write group.")
2229
# fast path same-url fetch operations
2230
if (self.has_same_location(source) and
2231
fetch_spec is None and
2232
self._has_same_fallbacks(source)):
2233
# check that last_revision is in 'from' and then return a
2235
if (revision_id is not None
2236
and not _mod_revision.is_null(revision_id)):
2237
self.get_revision(revision_id)
2238
return _mod_repository.FetchResult(0)
2239
# if there is no specific appropriate InterRepository, this will get
2240
# the InterRepository base class, which raises an
2241
# IncompatibleRepositories when asked to fetch.
2242
inter = _mod_repository.InterRepository.get(source, self)
2243
if (fetch_spec is not None
2244
and not getattr(inter, "supports_fetch_spec", False)):
2245
raise errors.UnsupportedOperation(
2246
"fetch_spec not supported for %r" % inter)
2247
return inter.fetch(revision_id=revision_id,
2248
find_ghosts=find_ghosts, fetch_spec=fetch_spec,
2251
def create_bundle(self, target, base, fileobj, format=None):
2253
self._real_repository.create_bundle(target, base, fileobj, format)
2255
def fileids_altered_by_revision_ids(self, revision_ids):
2257
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2259
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2261
return self._real_repository._get_versioned_file_checker(
2262
revisions, revision_versions_cache)
2264
def _iter_files_bytes_rpc(self, desired_files, absent):
2265
path = self.controldir._path_for_remote_call(self._client)
2268
for (file_id, revid, identifier) in desired_files:
2269
lines.append(b''.join([
2273
identifiers.append(identifier)
2274
(response_tuple, response_handler) = (
2275
self._call_with_body_bytes_expecting_body(
2276
b"Repository.iter_files_bytes", (path, ), b"\n".join(lines)))
2277
if response_tuple != (b'ok', ):
2278
response_handler.cancel_read_body()
2279
raise errors.UnexpectedSmartServerResponse(response_tuple)
2280
byte_stream = response_handler.read_streamed_body()
2282
def decompress_stream(start, byte_stream, unused):
2283
decompressor = zlib.decompressobj()
2284
yield decompressor.decompress(start)
2285
while decompressor.unused_data == b"":
2287
data = next(byte_stream)
2288
except StopIteration:
2290
yield decompressor.decompress(data)
2291
yield decompressor.flush()
2292
unused.append(decompressor.unused_data)
2295
while b"\n" not in unused:
2297
unused += next(byte_stream)
2298
except StopIteration:
2300
header, rest = unused.split(b"\n", 1)
2301
args = header.split(b"\0")
2302
if args[0] == b"absent":
2303
absent[identifiers[int(args[3])]] = (args[1], args[2])
2306
elif args[0] == b"ok":
2309
raise errors.UnexpectedSmartServerResponse(args)
2311
yield (identifiers[idx],
2312
decompress_stream(rest, byte_stream, unused_chunks))
2313
unused = b"".join(unused_chunks)
2315
def iter_files_bytes(self, desired_files):
2316
"""See Repository.iter_file_bytes.
2320
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2321
desired_files, absent):
2322
yield identifier, bytes_iterator
2323
for fallback in self._fallback_repositories:
2326
desired_files = [(key[0], key[1], identifier)
2327
for identifier, key in absent.items()]
2328
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2329
del absent[identifier]
2330
yield identifier, bytes_iterator
2332
# There may be more missing items, but raise an exception
2334
missing_identifier = next(iter(absent))
2335
missing_key = absent[missing_identifier]
2336
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2337
file_id=missing_key[0])
2338
except errors.UnknownSmartMethod:
2340
for (identifier, bytes_iterator) in (
2341
self._real_repository.iter_files_bytes(desired_files)):
2342
yield identifier, bytes_iterator
2344
def get_cached_parent_map(self, revision_ids):
2345
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2346
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2348
def get_parent_map(self, revision_ids):
2349
"""See breezy.Graph.get_parent_map()."""
2350
return self._make_parents_provider().get_parent_map(revision_ids)
2352
def _get_parent_map_rpc(self, keys):
2353
"""Helper for get_parent_map that performs the RPC."""
2354
medium = self._client._medium
2355
if medium._is_remote_before((1, 2)):
2356
# We already found out that the server can't understand
2357
# Repository.get_parent_map requests, so just fetch the whole
2360
# Note that this reads the whole graph, when only some keys are
2361
# wanted. On this old server there's no way (?) to get them all
2362
# in one go, and the user probably will have seen a warning about
2363
# the server being old anyhow.
2364
rg = self._get_revision_graph(None)
2365
# There is an API discrepancy between get_parent_map and
2366
# get_revision_graph. Specifically, a "key:()" pair in
2367
# get_revision_graph just means a node has no parents. For
2368
# "get_parent_map" it means the node is a ghost. So fix up the
2369
# graph to correct this.
2370
# https://bugs.launchpad.net/bzr/+bug/214894
2371
# There is one other "bug" which is that ghosts in
2372
# get_revision_graph() are not returned at all. But we won't worry
2373
# about that for now.
2374
for node_id, parent_ids in rg.items():
2375
if parent_ids == ():
2376
rg[node_id] = (NULL_REVISION,)
2377
rg[NULL_REVISION] = ()
2382
raise ValueError('get_parent_map(None) is not valid')
2383
if NULL_REVISION in keys:
2384
keys.discard(NULL_REVISION)
2385
found_parents = {NULL_REVISION: ()}
2387
return found_parents
2390
# TODO(Needs analysis): We could assume that the keys being requested
2391
# from get_parent_map are in a breadth first search, so typically they
2392
# will all be depth N from some common parent, and we don't have to
2393
# have the server iterate from the root parent, but rather from the
2394
# keys we're searching; and just tell the server the keyspace we
2395
# already have; but this may be more traffic again.
2397
# Transform self._parents_map into a search request recipe.
2398
# TODO: Manage this incrementally to avoid covering the same path
2399
# repeatedly. (The server will have to on each request, but the less
2400
# work done the better).
2402
# Negative caching notes:
2403
# new server sends missing when a request including the revid
2404
# 'include-missing:' is present in the request.
2405
# missing keys are serialised as missing:X, and we then call
2406
# provider.note_missing(X) for-all X
2407
parents_map = self._unstacked_provider.get_cached_map()
2408
if parents_map is None:
2409
# Repository is not locked, so there's no cache.
2411
if _DEFAULT_SEARCH_DEPTH <= 0:
2412
(start_set, stop_keys,
2413
key_count) = vf_search.search_result_from_parent_map(
2414
parents_map, self._unstacked_provider.missing_keys)
2416
(start_set, stop_keys,
2417
key_count) = vf_search.limited_search_result_from_parent_map(
2418
parents_map, self._unstacked_provider.missing_keys,
2419
keys, depth=_DEFAULT_SEARCH_DEPTH)
2420
recipe = ('manual', start_set, stop_keys, key_count)
2421
body = self._serialise_search_recipe(recipe)
2422
path = self.controldir._path_for_remote_call(self._client)
2424
if not isinstance(key, bytes):
2426
"key %r not a bytes string" % (key,))
2427
verb = b'Repository.get_parent_map'
2428
args = (path, b'include-missing:') + tuple(keys)
2430
response = self._call_with_body_bytes_expecting_body(
2432
except errors.UnknownSmartMethod:
2433
# Server does not support this method, so get the whole graph.
2434
# Worse, we have to force a disconnection, because the server now
2435
# doesn't realise it has a body on the wire to consume, so the
2436
# only way to recover is to abandon the connection.
2438
'Server is too old for fast get_parent_map, reconnecting. '
2439
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2441
# To avoid having to disconnect repeatedly, we keep track of the
2442
# fact the server doesn't understand remote methods added in 1.2.
2443
medium._remember_remote_is_before((1, 2))
2444
# Recurse just once and we should use the fallback code.
2445
return self._get_parent_map_rpc(keys)
2446
response_tuple, response_handler = response
2447
if response_tuple[0] not in [b'ok']:
2448
response_handler.cancel_read_body()
2449
raise errors.UnexpectedSmartServerResponse(response_tuple)
2450
if response_tuple[0] == b'ok':
2451
coded = bz2.decompress(response_handler.read_body_bytes())
2453
# no revisions found
2455
lines = coded.split(b'\n')
2458
d = tuple(line.split())
2460
revision_graph[d[0]] = d[1:]
2463
if d[0].startswith(b'missing:'):
2465
self._unstacked_provider.note_missing_key(revid)
2467
# no parents - so give the Graph result
2469
revision_graph[d[0]] = (NULL_REVISION,)
2470
return revision_graph
2472
def get_signature_text(self, revision_id):
2473
with self.lock_read():
2474
path = self.controldir._path_for_remote_call(self._client)
2476
response_tuple, response_handler = self._call_expecting_body(
2477
b'Repository.get_revision_signature_text', path, revision_id)
2478
except errors.UnknownSmartMethod:
2480
return self._real_repository.get_signature_text(revision_id)
2481
except errors.NoSuchRevision as err:
2482
for fallback in self._fallback_repositories:
2484
return fallback.get_signature_text(revision_id)
2485
except errors.NoSuchRevision:
2489
if response_tuple[0] != b'ok':
2490
raise errors.UnexpectedSmartServerResponse(response_tuple)
2491
return response_handler.read_body_bytes()
2493
def _get_inventory_xml(self, revision_id):
2494
with self.lock_read():
2495
# This call is used by older working tree formats,
2496
# which stored a serialized basis inventory.
2498
return self._real_repository._get_inventory_xml(revision_id)
2500
def reconcile(self, other=None, thorough=False):
2501
from ..reconcile import ReconcileResult
2502
with self.lock_write():
2503
path = self.controldir._path_for_remote_call(self._client)
2505
response, handler = self._call_expecting_body(
2506
b'Repository.reconcile', path, self._lock_token)
2507
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2509
return self._real_repository.reconcile(other=other, thorough=thorough)
2510
if response != (b'ok', ):
2511
raise errors.UnexpectedSmartServerResponse(response)
2512
body = handler.read_body_bytes()
2513
result = ReconcileResult()
2514
result.garbage_inventories = None
2515
result.inconsistent_parents = None
2516
result.aborted = None
2517
for line in body.split(b'\n'):
2520
key, val_text = line.split(b':')
2521
if key == b"garbage_inventories":
2522
result.garbage_inventories = int(val_text)
2523
elif key == b"inconsistent_parents":
2524
result.inconsistent_parents = int(val_text)
2526
mutter("unknown reconcile key %r" % key)
2529
def all_revision_ids(self):
2530
path = self.controldir._path_for_remote_call(self._client)
2532
response_tuple, response_handler = self._call_expecting_body(
2533
b"Repository.all_revision_ids", path)
2534
except errors.UnknownSmartMethod:
2536
return self._real_repository.all_revision_ids()
2537
if response_tuple != (b"ok", ):
2538
raise errors.UnexpectedSmartServerResponse(response_tuple)
2539
revids = set(response_handler.read_body_bytes().splitlines())
2540
for fallback in self._fallback_repositories:
2541
revids.update(set(fallback.all_revision_ids()))
2544
def _filtered_revision_trees(self, revision_ids, file_ids):
2545
"""Return Tree for a revision on this branch with only some files.
2547
:param revision_ids: a sequence of revision-ids;
2548
a revision-id may not be None or b'null:'
2549
:param file_ids: if not None, the result is filtered
2550
so that only those file-ids, their parents and their
2551
children are included.
2553
inventories = self.iter_inventories(revision_ids)
2554
for inv in inventories:
2555
# Should we introduce a FilteredRevisionTree class rather
2556
# than pre-filter the inventory here?
2557
filtered_inv = inv.filter(file_ids)
2558
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2560
def get_revision_delta(self, revision_id):
2561
with self.lock_read():
2562
r = self.get_revision(revision_id)
2563
return list(self.get_revision_deltas([r]))[0]
2565
def revision_trees(self, revision_ids):
2566
with self.lock_read():
2567
inventories = self.iter_inventories(revision_ids)
2568
for inv in inventories:
2569
yield RemoteInventoryTree(self, inv, inv.revision_id)
2571
def get_revision_reconcile(self, revision_id):
2572
with self.lock_read():
2574
return self._real_repository.get_revision_reconcile(revision_id)
2576
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2577
with self.lock_read():
2579
return self._real_repository.check(revision_ids=revision_ids,
2580
callback_refs=callback_refs, check_repo=check_repo)
2582
def copy_content_into(self, destination, revision_id=None):
2583
"""Make a complete copy of the content in self into destination.
2585
This is a destructive operation! Do not use it on existing
2588
interrepo = _mod_repository.InterRepository.get(self, destination)
2589
return interrepo.copy_content(revision_id)
2591
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2592
# get a tarball of the remote repository, and copy from that into the
2595
# TODO: Maybe a progress bar while streaming the tarball?
2596
note(gettext("Copying repository content as tarball..."))
2597
tar_file = self._get_tarball('bz2')
2598
if tar_file is None:
2600
destination = to_bzrdir.create_repository()
2602
tar = tarfile.open('repository', fileobj=tar_file,
2604
tmpdir = osutils.mkdtemp()
2606
tar.extractall(tmpdir)
2607
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2608
tmp_repo = tmp_bzrdir.open_repository()
2609
tmp_repo.copy_content_into(destination, revision_id)
2611
osutils.rmtree(tmpdir)
2615
# TODO: Suggestion from john: using external tar is much faster than
2616
# python's tarfile library, but it may not work on windows.
2619
def inventories(self):
2620
"""Decorate the real repository for now.
2622
In the long term a full blown network facility is needed to
2623
avoid creating a real repository object locally.
2626
return self._real_repository.inventories
2628
def pack(self, hint=None, clean_obsolete_packs=False):
2629
"""Compress the data within the repository.
2634
body = b"".join([l.encode('ascii') + b"\n" for l in hint])
2635
with self.lock_write():
2636
path = self.controldir._path_for_remote_call(self._client)
2638
response, handler = self._call_with_body_bytes_expecting_body(
2639
b'Repository.pack', (path, self._lock_token,
2640
str(clean_obsolete_packs).encode('ascii')), body)
2641
except errors.UnknownSmartMethod:
2643
return self._real_repository.pack(hint=hint,
2644
clean_obsolete_packs=clean_obsolete_packs)
2645
handler.cancel_read_body()
2646
if response != (b'ok', ):
2647
raise errors.UnexpectedSmartServerResponse(response)
2650
def revisions(self):
2651
"""Decorate the real repository for now.
2653
In the long term a full blown network facility is needed.
2656
return self._real_repository.revisions
2658
def set_make_working_trees(self, new_value):
2660
new_value_str = b"True"
2662
new_value_str = b"False"
2663
path = self.controldir._path_for_remote_call(self._client)
2665
response = self._call(
2666
b'Repository.set_make_working_trees', path, new_value_str)
2667
except errors.UnknownSmartMethod:
2669
self._real_repository.set_make_working_trees(new_value)
2671
if response[0] != b'ok':
2672
raise errors.UnexpectedSmartServerResponse(response)
2675
def signatures(self):
2676
"""Decorate the real repository for now.
2678
In the long term a full blown network facility is needed to avoid
2679
creating a real repository object locally.
2682
return self._real_repository.signatures
2684
def sign_revision(self, revision_id, gpg_strategy):
2685
with self.lock_write():
2686
testament = _mod_testament.Testament.from_revision(
2688
plaintext = testament.as_short_text()
2689
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2693
"""Decorate the real repository for now.
2695
In the long term a full blown network facility is needed to avoid
2696
creating a real repository object locally.
2699
return self._real_repository.texts
2701
def _iter_revisions_rpc(self, revision_ids):
2702
body = b"\n".join(revision_ids)
2703
path = self.controldir._path_for_remote_call(self._client)
2704
response_tuple, response_handler = (
2705
self._call_with_body_bytes_expecting_body(
2706
b"Repository.iter_revisions", (path, ), body))
2707
if response_tuple[0] != b"ok":
2708
raise errors.UnexpectedSmartServerResponse(response_tuple)
2709
serializer_format = response_tuple[1].decode('ascii')
2710
serializer = serializer_format_registry.get(serializer_format)
2711
byte_stream = response_handler.read_streamed_body()
2712
decompressor = zlib.decompressobj()
2714
for bytes in byte_stream:
2715
chunks.append(decompressor.decompress(bytes))
2716
if decompressor.unused_data != b"":
2717
chunks.append(decompressor.flush())
2718
yield serializer.read_revision_from_string(b"".join(chunks))
2719
unused = decompressor.unused_data
2720
decompressor = zlib.decompressobj()
2721
chunks = [decompressor.decompress(unused)]
2722
chunks.append(decompressor.flush())
2723
text = b"".join(chunks)
2725
yield serializer.read_revision_from_string(b"".join(chunks))
2727
def iter_revisions(self, revision_ids):
2728
for rev_id in revision_ids:
2729
if not rev_id or not isinstance(rev_id, bytes):
2730
raise errors.InvalidRevisionId(
2731
revision_id=rev_id, branch=self)
2732
with self.lock_read():
2734
missing = set(revision_ids)
2735
for rev in self._iter_revisions_rpc(revision_ids):
2736
missing.remove(rev.revision_id)
2737
yield (rev.revision_id, rev)
2738
for fallback in self._fallback_repositories:
2741
for (revid, rev) in fallback.iter_revisions(missing):
2744
missing.remove(revid)
2745
for revid in missing:
2747
except errors.UnknownSmartMethod:
2749
for entry in self._real_repository.iter_revisions(revision_ids):
2752
def supports_rich_root(self):
2753
return self._format.rich_root_data
2756
def _serializer(self):
2757
return self._format._serializer
2759
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2760
with self.lock_write():
2761
signature = gpg_strategy.sign(plaintext, gpg.MODE_CLEAR)
2762
self.add_signature_text(revision_id, signature)
2764
def add_signature_text(self, revision_id, signature):
2765
if self._real_repository:
2766
# If there is a real repository the write group will
2767
# be in the real repository as well, so use that:
2769
return self._real_repository.add_signature_text(
2770
revision_id, signature)
2771
path = self.controldir._path_for_remote_call(self._client)
2772
response, handler = self._call_with_body_bytes_expecting_body(
2773
b'Repository.add_signature_text', (path, self._lock_token,
2775
tuple([token.encode('utf-8')
2776
for token in self._write_group_tokens]),
2778
handler.cancel_read_body()
2780
if response[0] != b'ok':
2781
raise errors.UnexpectedSmartServerResponse(response)
2782
self._write_group_tokens = [token.decode(
2783
'utf-8') for token in response[1:]]
2785
def has_signature_for_revision_id(self, revision_id):
2786
path = self.controldir._path_for_remote_call(self._client)
2788
response = self._call(b'Repository.has_signature_for_revision_id',
2790
except errors.UnknownSmartMethod:
2792
return self._real_repository.has_signature_for_revision_id(
2794
if response[0] not in (b'yes', b'no'):
2795
raise SmartProtocolError(
2796
'unexpected response code %s' % (response,))
2797
if response[0] == b'yes':
2799
for fallback in self._fallback_repositories:
2800
if fallback.has_signature_for_revision_id(revision_id):
2804
def verify_revision_signature(self, revision_id, gpg_strategy):
2805
with self.lock_read():
2806
if not self.has_signature_for_revision_id(revision_id):
2807
return gpg.SIGNATURE_NOT_SIGNED, None
2808
signature = self.get_signature_text(revision_id)
2810
testament = _mod_testament.Testament.from_revision(
2813
(status, key, signed_plaintext) = gpg_strategy.verify(signature)
2814
if testament.as_short_text() != signed_plaintext:
2815
return gpg.SIGNATURE_NOT_VALID, None
2816
return (status, key)
2818
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2820
return self._real_repository.item_keys_introduced_by(revision_ids,
2821
_files_pb=_files_pb)
2823
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2825
return self._real_repository._find_inconsistent_revision_parents(
2828
def _check_for_inconsistent_revision_parents(self):
2830
return self._real_repository._check_for_inconsistent_revision_parents()
2832
def _make_parents_provider(self, other=None):
2833
providers = [self._unstacked_provider]
2834
if other is not None:
2835
providers.insert(0, other)
2836
return graph.StackedParentsProvider(_LazyListJoin(
2837
providers, self._fallback_repositories))
2839
def _serialise_search_recipe(self, recipe):
2840
"""Serialise a graph search recipe.
2842
:param recipe: A search recipe (start, stop, count).
2843
:return: Serialised bytes.
2845
start_keys = b' '.join(recipe[1])
2846
stop_keys = b' '.join(recipe[2])
2847
count = str(recipe[3]).encode('ascii')
2848
return b'\n'.join((start_keys, stop_keys, count))
2850
def _serialise_search_result(self, search_result):
2851
parts = search_result.get_network_struct()
2852
return b'\n'.join(parts)
2855
path = self.controldir._path_for_remote_call(self._client)
2857
response = self._call(b'PackRepository.autopack', path)
2858
except errors.UnknownSmartMethod:
2860
self._real_repository._pack_collection.autopack()
2863
if response[0] != b'ok':
2864
raise errors.UnexpectedSmartServerResponse(response)
2866
def _revision_archive(self, revision_id, format, name, root, subdir,
2868
path = self.controldir._path_for_remote_call(self._client)
2869
format = format or ''
2871
subdir = subdir or ''
2872
force_mtime = int(force_mtime) if force_mtime is not None else None
2874
response, protocol = self._call_expecting_body(
2875
b'Repository.revision_archive', path,
2877
format.encode('ascii'),
2878
os.path.basename(name).encode('utf-8'),
2879
root.encode('utf-8'),
2880
subdir.encode('utf-8'),
2882
except errors.UnknownSmartMethod:
2884
if response[0] == b'ok':
2885
return iter([protocol.read_body_bytes()])
2886
raise errors.UnexpectedSmartServerResponse(response)
2888
def _annotate_file_revision(self, revid, tree_path, file_id, default_revision):
2889
path = self.controldir._path_for_remote_call(self._client)
2890
tree_path = tree_path.encode('utf-8')
2891
file_id = file_id or b''
2892
default_revision = default_revision or b''
2894
response, handler = self._call_expecting_body(
2895
b'Repository.annotate_file_revision', path,
2896
revid, tree_path, file_id, default_revision)
2897
except errors.UnknownSmartMethod:
2899
if response[0] != b'ok':
2900
raise errors.UnexpectedSmartServerResponse(response)
2901
return map(tuple, bencode.bdecode(handler.read_body_bytes()))
2904
class RemoteStreamSink(vf_repository.StreamSink):
2906
def _insert_real(self, stream, src_format, resume_tokens):
2907
self.target_repo._ensure_real()
2908
sink = self.target_repo._real_repository._get_sink()
2909
result = sink.insert_stream(stream, src_format, resume_tokens)
2911
self.target_repo.autopack()
2914
def insert_missing_keys(self, source, missing_keys):
2915
if (isinstance(source, RemoteStreamSource)
2916
and source.from_repository._client._medium == self.target_repo._client._medium):
2917
# Streaming from and to the same medium is tricky, since we don't support
2918
# more than one concurrent request. For now, just force VFS.
2919
stream = source._get_real_stream_for_missing_keys(missing_keys)
2921
stream = source.get_stream_for_missing_keys(missing_keys)
2922
return self.insert_stream_without_locking(stream,
2923
self.target_repo._format)
2925
def insert_stream(self, stream, src_format, resume_tokens):
2926
target = self.target_repo
2927
target._unstacked_provider.missing_keys.clear()
2928
candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
2929
if target._lock_token:
2930
candidate_calls.append(
2931
(b'Repository.insert_stream_locked', (1, 14)))
2932
lock_args = (target._lock_token or b'',)
2934
candidate_calls.append((b'Repository.insert_stream', (1, 13)))
2936
client = target._client
2937
medium = client._medium
2938
path = target.controldir._path_for_remote_call(client)
2939
# Probe for the verb to use with an empty stream before sending the
2940
# real stream to it. We do this both to avoid the risk of sending a
2941
# large request that is then rejected, and because we don't want to
2942
# implement a way to buffer, rewind, or restart the stream.
2944
for verb, required_version in candidate_calls:
2945
if medium._is_remote_before(required_version):
2948
# We've already done the probing (and set _is_remote_before) on
2949
# a previous insert.
2952
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2954
response = client.call_with_body_stream(
2955
(verb, path, b'') + lock_args, byte_stream)
2956
except errors.UnknownSmartMethod:
2957
medium._remember_remote_is_before(required_version)
2963
return self._insert_real(stream, src_format, resume_tokens)
2964
self._last_inv_record = None
2965
self._last_substream = None
2966
if required_version < (1, 19):
2967
# Remote side doesn't support inventory deltas. Wrap the stream to
2968
# make sure we don't send any. If the stream contains inventory
2969
# deltas we'll interrupt the smart insert_stream request and
2971
stream = self._stop_stream_if_inventory_delta(stream)
2972
byte_stream = smart_repo._stream_to_byte_stream(
2974
resume_tokens = b' '.join([token.encode('utf-8')
2975
for token in resume_tokens])
2976
response = client.call_with_body_stream(
2977
(verb, path, resume_tokens) + lock_args, byte_stream)
2978
if response[0][0] not in (b'ok', b'missing-basis'):
2979
raise errors.UnexpectedSmartServerResponse(response)
2980
if self._last_substream is not None:
2981
# The stream included an inventory-delta record, but the remote
2982
# side isn't new enough to support them. So we need to send the
2983
# rest of the stream via VFS.
2984
self.target_repo.refresh_data()
2985
return self._resume_stream_with_vfs(response, src_format)
2986
if response[0][0] == b'missing-basis':
2987
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2988
resume_tokens = [token.decode('utf-8') for token in tokens]
2989
return resume_tokens, set((entry[0].decode('utf-8'), ) + entry[1:] for entry in missing_keys)
2991
self.target_repo.refresh_data()
2994
def _resume_stream_with_vfs(self, response, src_format):
2995
"""Resume sending a stream via VFS, first resending the record and
2996
substream that couldn't be sent via an insert_stream verb.
2998
if response[0][0] == b'missing-basis':
2999
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3000
tokens = [token.decode('utf-8') for token in tokens]
3001
# Ignore missing_keys, we haven't finished inserting yet
3005
def resume_substream():
3006
# Yield the substream that was interrupted.
3007
for record in self._last_substream:
3009
self._last_substream = None
3011
def resume_stream():
3012
# Finish sending the interrupted substream
3013
yield ('inventory-deltas', resume_substream())
3014
# Then simply continue sending the rest of the stream.
3015
for substream_kind, substream in self._last_stream:
3016
yield substream_kind, substream
3017
return self._insert_real(resume_stream(), src_format, tokens)
3019
def _stop_stream_if_inventory_delta(self, stream):
3020
"""Normally this just lets the original stream pass-through unchanged.
3022
However if any 'inventory-deltas' substream occurs it will stop
3023
streaming, and store the interrupted substream and stream in
3024
self._last_substream and self._last_stream so that the stream can be
3025
resumed by _resume_stream_with_vfs.
3028
stream_iter = iter(stream)
3029
for substream_kind, substream in stream_iter:
3030
if substream_kind == 'inventory-deltas':
3031
self._last_substream = substream
3032
self._last_stream = stream_iter
3035
yield substream_kind, substream
3038
class RemoteStreamSource(vf_repository.StreamSource):
3039
"""Stream data from a remote server."""
3041
def get_stream(self, search):
3042
if (self.from_repository._fallback_repositories
3043
and self.to_format._fetch_order == 'topological'):
3044
return self._real_stream(self.from_repository, search)
3047
repos = [self.from_repository]
3053
repos.extend(repo._fallback_repositories)
3054
sources.append(repo)
3055
return self.missing_parents_chain(search, sources)
3057
def _get_real_stream_for_missing_keys(self, missing_keys):
3058
self.from_repository._ensure_real()
3059
real_repo = self.from_repository._real_repository
3060
real_source = real_repo._get_source(self.to_format)
3061
return real_source.get_stream_for_missing_keys(missing_keys)
3063
def get_stream_for_missing_keys(self, missing_keys):
3064
if not isinstance(self.from_repository, RemoteRepository):
3065
return self._get_real_stream_for_missing_keys(missing_keys)
3066
client = self.from_repository._client
3067
medium = client._medium
3068
if medium._is_remote_before((3, 0)):
3069
return self._get_real_stream_for_missing_keys(missing_keys)
3070
path = self.from_repository.controldir._path_for_remote_call(client)
3071
args = (path, self.to_format.network_name())
3072
search_bytes = b'\n'.join(
3073
[b'%s\t%s' % (key[0].encode('utf-8'), key[1]) for key in missing_keys])
3075
response, handler = self.from_repository._call_with_body_bytes_expecting_body(
3076
b'Repository.get_stream_for_missing_keys', args, search_bytes)
3077
except (errors.UnknownSmartMethod, errors.UnknownFormatError):
3078
return self._get_real_stream_for_missing_keys(missing_keys)
3079
if response[0] != b'ok':
3080
raise errors.UnexpectedSmartServerResponse(response)
3081
byte_stream = handler.read_streamed_body()
3082
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3083
self._record_counter)
3084
if src_format.network_name() != self.from_repository._format.network_name():
3085
raise AssertionError(
3086
"Mismatched RemoteRepository and stream src %r, %r" % (
3087
src_format.network_name(), repo._format.network_name()))
3090
def _real_stream(self, repo, search):
3091
"""Get a stream for search from repo.
3093
This never called RemoteStreamSource.get_stream, and is a helper
3094
for RemoteStreamSource._get_stream to allow getting a stream
3095
reliably whether fallback back because of old servers or trying
3096
to stream from a non-RemoteRepository (which the stacked support
3099
source = repo._get_source(self.to_format)
3100
if isinstance(source, RemoteStreamSource):
3102
source = repo._real_repository._get_source(self.to_format)
3103
return source.get_stream(search)
3105
def _get_stream(self, repo, search):
3106
"""Core worker to get a stream from repo for search.
3108
This is used by both get_stream and the stacking support logic. It
3109
deliberately gets a stream for repo which does not need to be
3110
self.from_repository. In the event that repo is not Remote, or
3111
cannot do a smart stream, a fallback is made to the generic
3112
repository._get_stream() interface, via self._real_stream.
3114
In the event of stacking, streams from _get_stream will not
3115
contain all the data for search - this is normal (see get_stream).
3117
:param repo: A repository.
3118
:param search: A search.
3120
# Fallbacks may be non-smart
3121
if not isinstance(repo, RemoteRepository):
3122
return self._real_stream(repo, search)
3123
client = repo._client
3124
medium = client._medium
3125
path = repo.controldir._path_for_remote_call(client)
3126
search_bytes = repo._serialise_search_result(search)
3127
args = (path, self.to_format.network_name())
3129
(b'Repository.get_stream_1.19', (1, 19)),
3130
(b'Repository.get_stream', (1, 13))]
3133
for verb, version in candidate_verbs:
3134
if medium._is_remote_before(version):
3137
response = repo._call_with_body_bytes_expecting_body(
3138
verb, args, search_bytes)
3139
except errors.UnknownSmartMethod:
3140
medium._remember_remote_is_before(version)
3141
except errors.UnknownErrorFromSmartServer as e:
3142
if isinstance(search, vf_search.EverythingResult):
3143
error_verb = e.error_from_smart_server.error_verb
3144
if error_verb == b'BadSearch':
3145
# Pre-2.4 servers don't support this sort of search.
3146
# XXX: perhaps falling back to VFS on BadSearch is a
3147
# good idea in general? It might provide a little bit
3148
# of protection against client-side bugs.
3149
medium._remember_remote_is_before((2, 4))
3153
response_tuple, response_handler = response
3157
return self._real_stream(repo, search)
3158
if response_tuple[0] != b'ok':
3159
raise errors.UnexpectedSmartServerResponse(response_tuple)
3160
byte_stream = response_handler.read_streamed_body()
3161
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3162
self._record_counter)
3163
if src_format.network_name() != repo._format.network_name():
3164
raise AssertionError(
3165
"Mismatched RemoteRepository and stream src %r, %r" % (
3166
src_format.network_name(), repo._format.network_name()))
3169
def missing_parents_chain(self, search, sources):
3170
"""Chain multiple streams together to handle stacking.
3172
:param search: The overall search to satisfy with streams.
3173
:param sources: A list of Repository objects to query.
3175
self.from_serialiser = self.from_repository._format._serializer
3176
self.seen_revs = set()
3177
self.referenced_revs = set()
3178
# If there are heads in the search, or the key count is > 0, we are not
3180
while not search.is_empty() and len(sources) > 1:
3181
source = sources.pop(0)
3182
stream = self._get_stream(source, search)
3183
for kind, substream in stream:
3184
if kind != 'revisions':
3185
yield kind, substream
3187
yield kind, self.missing_parents_rev_handler(substream)
3188
search = search.refine(self.seen_revs, self.referenced_revs)
3189
self.seen_revs = set()
3190
self.referenced_revs = set()
3191
if not search.is_empty():
3192
for kind, stream in self._get_stream(sources[0], search):
3195
def missing_parents_rev_handler(self, substream):
3196
for content in substream:
3197
revision_bytes = content.get_bytes_as('fulltext')
3198
revision = self.from_serialiser.read_revision_from_string(
3200
self.seen_revs.add(content.key[-1])
3201
self.referenced_revs.update(revision.parent_ids)
3205
class RemoteBranchLockableFiles(LockableFiles):
3206
"""A 'LockableFiles' implementation that talks to a smart server.
3208
This is not a public interface class.
3211
def __init__(self, bzrdir, _client):
3212
self.controldir = bzrdir
3213
self._client = _client
3214
self._need_find_modes = True
3215
LockableFiles.__init__(
3216
self, bzrdir.get_branch_transport(None),
3217
'lock', lockdir.LockDir)
3219
def _find_modes(self):
3220
# RemoteBranches don't let the client set the mode of control files.
3221
self._dir_mode = None
3222
self._file_mode = None
3225
class RemoteBranchFormat(branch.BranchFormat):
3227
def __init__(self, network_name=None):
3228
super(RemoteBranchFormat, self).__init__()
3229
self._matchingcontroldir = RemoteBzrDirFormat()
3230
self._matchingcontroldir.set_branch_format(self)
3231
self._custom_format = None
3232
self._network_name = network_name
3234
def __eq__(self, other):
3235
return (isinstance(other, RemoteBranchFormat)
3236
and self.__dict__ == other.__dict__)
3238
def _ensure_real(self):
3239
if self._custom_format is None:
3241
self._custom_format = branch.network_format_registry.get(
3244
raise errors.UnknownFormatError(kind='branch',
3245
format=self._network_name)
3247
def get_format_description(self):
3249
return 'Remote: ' + self._custom_format.get_format_description()
3251
def network_name(self):
3252
return self._network_name
3254
def open(self, a_controldir, name=None, ignore_fallbacks=False):
3255
return a_controldir.open_branch(name=name,
3256
ignore_fallbacks=ignore_fallbacks)
3258
def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3260
# Initialisation when using a local bzrdir object, or a non-vfs init
3261
# method is not available on the server.
3262
# self._custom_format is always set - the start of initialize ensures
3264
if isinstance(a_controldir, RemoteBzrDir):
3265
a_controldir._ensure_real()
3266
result = self._custom_format.initialize(a_controldir._real_bzrdir,
3267
name=name, append_revisions_only=append_revisions_only,
3268
repository=repository)
3270
# We assume the bzrdir is parameterised; it may not be.
3271
result = self._custom_format.initialize(a_controldir, name=name,
3272
append_revisions_only=append_revisions_only,
3273
repository=repository)
3274
if (isinstance(a_controldir, RemoteBzrDir)
3275
and not isinstance(result, RemoteBranch)):
3276
result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
3280
def initialize(self, a_controldir, name=None, repository=None,
3281
append_revisions_only=None):
3283
name = a_controldir._get_selected_branch()
3284
# 1) get the network name to use.
3285
if self._custom_format:
3286
network_name = self._custom_format.network_name()
3288
# Select the current breezy default and ask for that.
3289
reference_bzrdir_format = controldir.format_registry.get(
3291
reference_format = reference_bzrdir_format.get_branch_format()
3292
self._custom_format = reference_format
3293
network_name = reference_format.network_name()
3294
# Being asked to create on a non RemoteBzrDir:
3295
if not isinstance(a_controldir, RemoteBzrDir):
3296
return self._vfs_initialize(a_controldir, name=name,
3297
append_revisions_only=append_revisions_only,
3298
repository=repository)
3299
medium = a_controldir._client._medium
3300
if medium._is_remote_before((1, 13)):
3301
return self._vfs_initialize(a_controldir, name=name,
3302
append_revisions_only=append_revisions_only,
3303
repository=repository)
3304
# Creating on a remote bzr dir.
3305
# 2) try direct creation via RPC
3306
path = a_controldir._path_for_remote_call(a_controldir._client)
3308
# XXX JRV20100304: Support creating colocated branches
3309
raise errors.NoColocatedBranchSupport(self)
3310
verb = b'BzrDir.create_branch'
3312
response = a_controldir._call(verb, path, network_name)
3313
except errors.UnknownSmartMethod:
3314
# Fallback - use vfs methods
3315
medium._remember_remote_is_before((1, 13))
3316
return self._vfs_initialize(a_controldir, name=name,
3317
append_revisions_only=append_revisions_only,
3318
repository=repository)
3319
if response[0] != b'ok':
3320
raise errors.UnexpectedSmartServerResponse(response)
3321
# Turn the response into a RemoteRepository object.
3322
format = RemoteBranchFormat(network_name=response[1])
3323
repo_format = response_tuple_to_repo_format(response[3:])
3324
repo_path = response[2].decode('utf-8')
3325
if repository is not None:
3326
remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3327
url_diff = urlutils.relative_url(repository.user_url,
3330
raise AssertionError(
3331
'repository.user_url %r does not match URL from server '
3332
'response (%r + %r)'
3333
% (repository.user_url, a_controldir.user_url, repo_path))
3334
remote_repo = repository
3337
repo_bzrdir = a_controldir
3339
repo_bzrdir = RemoteBzrDir(
3340
a_controldir.root_transport.clone(
3341
repo_path), a_controldir._format,
3342
a_controldir._client)
3343
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3344
remote_branch = RemoteBranch(a_controldir, remote_repo,
3345
format=format, setup_stacking=False, name=name)
3346
if append_revisions_only:
3347
remote_branch.set_append_revisions_only(append_revisions_only)
3348
# XXX: We know this is a new branch, so it must have revno 0, revid
3349
# NULL_REVISION. Creating the branch locked would make this be unable
3350
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3351
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3352
return remote_branch
3354
def make_tags(self, branch):
3356
return self._custom_format.make_tags(branch)
3358
def supports_tags(self):
3359
# Remote branches might support tags, but we won't know until we
3360
# access the real remote branch.
3362
return self._custom_format.supports_tags()
3364
def supports_stacking(self):
3366
return self._custom_format.supports_stacking()
3368
def supports_set_append_revisions_only(self):
3370
return self._custom_format.supports_set_append_revisions_only()
3373
def supports_reference_locations(self):
3375
return self._custom_format.supports_reference_locations
3377
def stores_revno(self):
3380
def _use_default_local_heads_to_fetch(self):
3381
# If the branch format is a metadir format *and* its heads_to_fetch
3382
# implementation is not overridden vs the base class, we can use the
3383
# base class logic rather than use the heads_to_fetch RPC. This is
3384
# usually cheaper in terms of net round trips, as the last-revision and
3385
# tags info fetched is cached and would be fetched anyway.
3387
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3388
branch_class = self._custom_format._branch_class()
3389
heads_to_fetch_impl = branch_class.heads_to_fetch
3390
if heads_to_fetch_impl is branch.Branch.heads_to_fetch:
3395
class RemoteBranchStore(_mod_config.IniFileStore):
3396
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3398
Note that this is specific to bzr-based formats.
3401
def __init__(self, branch):
3402
super(RemoteBranchStore, self).__init__()
3403
self.branch = branch
3405
self._real_store = None
3407
def external_url(self):
3408
return urlutils.join(self.branch.user_url, 'branch.conf')
3410
def _load_content(self):
3411
path = self.branch._remote_path()
3413
response, handler = self.branch._call_expecting_body(
3414
b'Branch.get_config_file', path)
3415
except errors.UnknownSmartMethod:
3417
return self._real_store._load_content()
3418
if len(response) and response[0] != b'ok':
3419
raise errors.UnexpectedSmartServerResponse(response)
3420
return handler.read_body_bytes()
3422
def _save_content(self, content):
3423
path = self.branch._remote_path()
3425
response, handler = self.branch._call_with_body_bytes_expecting_body(
3426
b'Branch.put_config_file', (path,
3427
self.branch._lock_token, self.branch._repo_lock_token),
3429
except errors.UnknownSmartMethod:
3431
return self._real_store._save_content(content)
3432
handler.cancel_read_body()
3433
if response != (b'ok', ):
3434
raise errors.UnexpectedSmartServerResponse(response)
3436
def _ensure_real(self):
3437
self.branch._ensure_real()
3438
if self._real_store is None:
3439
self._real_store = _mod_config.BranchStore(self.branch)
3442
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3443
"""Branch stored on a server accessed by HPSS RPC.
3445
At the moment most operations are mapped down to simple file operations.
3448
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3449
_client=None, format=None, setup_stacking=True, name=None,
3450
possible_transports=None):
3451
"""Create a RemoteBranch instance.
3453
:param real_branch: An optional local implementation of the branch
3454
format, usually accessing the data via the VFS.
3455
:param _client: Private parameter for testing.
3456
:param format: A RemoteBranchFormat object, None to create one
3457
automatically. If supplied it should have a network_name already
3459
:param setup_stacking: If True make an RPC call to determine the
3460
stacked (or not) status of the branch. If False assume the branch
3462
:param name: Colocated branch name
3464
# We intentionally don't call the parent class's __init__, because it
3465
# will try to assign to self.tags, which is a property in this subclass.
3466
# And the parent's __init__ doesn't do much anyway.
3467
self.controldir = remote_bzrdir
3469
if _client is not None:
3470
self._client = _client
3472
self._client = remote_bzrdir._client
3473
self.repository = remote_repository
3474
if real_branch is not None:
3475
self._real_branch = real_branch
3476
# Give the remote repository the matching real repo.
3477
real_repo = self._real_branch.repository
3478
if isinstance(real_repo, RemoteRepository):
3479
real_repo._ensure_real()
3480
real_repo = real_repo._real_repository
3481
self.repository._set_real_repository(real_repo)
3482
# Give the branch the remote repository to let fast-pathing happen.
3483
self._real_branch.repository = self.repository
3485
self._real_branch = None
3486
# Fill out expected attributes of branch for breezy API users.
3487
self._clear_cached_state()
3488
# TODO: deprecate self.base in favor of user_url
3489
self.base = self.controldir.user_url
3491
self._control_files = None
3492
self._lock_mode = None
3493
self._lock_token = None
3494
self._repo_lock_token = None
3495
self._lock_count = 0
3496
self._leave_lock = False
3497
self.conf_store = None
3498
# Setup a format: note that we cannot call _ensure_real until all the
3499
# attributes above are set: This code cannot be moved higher up in this
3502
self._format = RemoteBranchFormat()
3503
if real_branch is not None:
3504
self._format._network_name = \
3505
self._real_branch._format.network_name()
3507
self._format = format
3508
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3509
# branch.open_branch method.
3510
self._real_ignore_fallbacks = not setup_stacking
3511
if not self._format._network_name:
3512
# Did not get from open_branchV2 - old server.
3514
self._format._network_name = \
3515
self._real_branch._format.network_name()
3516
self.tags = self._format.make_tags(self)
3517
# The base class init is not called, so we duplicate this:
3518
hooks = branch.Branch.hooks['open']
3521
self._is_stacked = False
3523
self._setup_stacking(possible_transports)
3525
def _setup_stacking(self, possible_transports):
3526
# configure stacking into the remote repository, by reading it from
3529
fallback_url = self.get_stacked_on_url()
3530
except (errors.NotStacked, branch.UnstackableBranchFormat,
3531
errors.UnstackableRepositoryFormat) as e:
3533
self._is_stacked = True
3534
if possible_transports is None:
3535
possible_transports = []
3537
possible_transports = list(possible_transports)
3538
possible_transports.append(self.controldir.root_transport)
3539
self._activate_fallback_location(fallback_url,
3540
possible_transports=possible_transports)
3542
def _get_config(self):
3543
return RemoteBranchConfig(self)
3545
def _get_config_store(self):
3546
if self.conf_store is None:
3547
self.conf_store = RemoteBranchStore(self)
3548
return self.conf_store
3550
def store_uncommitted(self, creator):
3552
return self._real_branch.store_uncommitted(creator)
3554
def get_unshelver(self, tree):
3556
return self._real_branch.get_unshelver(tree)
3558
def _get_real_transport(self):
3559
# if we try vfs access, return the real branch's vfs transport
3561
return self._real_branch._transport
3563
_transport = property(_get_real_transport)
3566
return "%s(%s)" % (self.__class__.__name__, self.base)
3570
def _ensure_real(self):
3571
"""Ensure that there is a _real_branch set.
3573
Used before calls to self._real_branch.
3575
if self._real_branch is None:
3576
if not vfs.vfs_enabled():
3577
raise AssertionError('smart server vfs must be enabled '
3578
'to use vfs implementation')
3579
self.controldir._ensure_real()
3580
self._real_branch = self.controldir._real_bzrdir.open_branch(
3581
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3582
# The remote branch and the real branch shares the same store. If
3583
# we don't, there will always be cases where one of the stores
3584
# doesn't see an update made on the other.
3585
self._real_branch.conf_store = self.conf_store
3586
if self.repository._real_repository is None:
3587
# Give the remote repository the matching real repo.
3588
real_repo = self._real_branch.repository
3589
if isinstance(real_repo, RemoteRepository):
3590
real_repo._ensure_real()
3591
real_repo = real_repo._real_repository
3592
self.repository._set_real_repository(real_repo)
3593
# Give the real branch the remote repository to let fast-pathing
3595
self._real_branch.repository = self.repository
3596
if self._lock_mode == 'r':
3597
self._real_branch.lock_read()
3598
elif self._lock_mode == 'w':
3599
self._real_branch.lock_write(token=self._lock_token)
3601
def _translate_error(self, err, **context):
3602
self.repository._translate_error(err, branch=self, **context)
3604
def _clear_cached_state(self):
3605
super(RemoteBranch, self)._clear_cached_state()
3606
self._tags_bytes = None
3607
if self._real_branch is not None:
3608
self._real_branch._clear_cached_state()
3610
def _clear_cached_state_of_remote_branch_only(self):
3611
"""Like _clear_cached_state, but doesn't clear the cache of
3614
This is useful when falling back to calling a method of
3615
self._real_branch that changes state. In that case the underlying
3616
branch changes, so we need to invalidate this RemoteBranch's cache of
3617
it. However, there's no need to invalidate the _real_branch's cache
3618
too, in fact doing so might harm performance.
3620
super(RemoteBranch, self)._clear_cached_state()
3623
def control_files(self):
3624
# Defer actually creating RemoteBranchLockableFiles until its needed,
3625
# because it triggers an _ensure_real that we otherwise might not need.
3626
if self._control_files is None:
3627
self._control_files = RemoteBranchLockableFiles(
3628
self.controldir, self._client)
3629
return self._control_files
3631
def get_physical_lock_status(self):
3632
"""See Branch.get_physical_lock_status()."""
3634
response = self._client.call(b'Branch.get_physical_lock_status',
3635
self._remote_path())
3636
except errors.UnknownSmartMethod:
3638
return self._real_branch.get_physical_lock_status()
3639
if response[0] not in (b'yes', b'no'):
3640
raise errors.UnexpectedSmartServerResponse(response)
3641
return (response[0] == b'yes')
3643
def get_stacked_on_url(self):
3644
"""Get the URL this branch is stacked against.
3646
:raises NotStacked: If the branch is not stacked.
3647
:raises UnstackableBranchFormat: If the branch does not support
3649
:raises UnstackableRepositoryFormat: If the repository does not support
3653
# there may not be a repository yet, so we can't use
3654
# self._translate_error, so we can't use self._call either.
3655
response = self._client.call(b'Branch.get_stacked_on_url',
3656
self._remote_path())
3657
except errors.ErrorFromSmartServer as err:
3658
# there may not be a repository yet, so we can't call through
3659
# its _translate_error
3660
_translate_error(err, branch=self)
3661
except errors.UnknownSmartMethod as err:
3663
return self._real_branch.get_stacked_on_url()
3664
if response[0] != b'ok':
3665
raise errors.UnexpectedSmartServerResponse(response)
3666
return response[1].decode('utf-8')
3668
def set_stacked_on_url(self, url):
3669
branch.Branch.set_stacked_on_url(self, url)
3670
# We need the stacked_on_url to be visible both locally (to not query
3671
# it repeatedly) and remotely (so smart verbs can get it server side)
3672
# Without the following line,
3673
# breezy.tests.per_branch.test_create_clone.TestCreateClone
3674
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3675
# fails for remote branches -- vila 2012-01-04
3676
self.conf_store.save_changes()
3678
self._is_stacked = False
3680
self._is_stacked = True
3682
def _vfs_get_tags_bytes(self):
3684
return self._real_branch._get_tags_bytes()
3686
def _get_tags_bytes(self):
3687
with self.lock_read():
3688
if self._tags_bytes is None:
3689
self._tags_bytes = self._get_tags_bytes_via_hpss()
3690
return self._tags_bytes
3692
def _get_tags_bytes_via_hpss(self):
3693
medium = self._client._medium
3694
if medium._is_remote_before((1, 13)):
3695
return self._vfs_get_tags_bytes()
3697
response = self._call(
3698
b'Branch.get_tags_bytes', self._remote_path())
3699
except errors.UnknownSmartMethod:
3700
medium._remember_remote_is_before((1, 13))
3701
return self._vfs_get_tags_bytes()
3704
def _vfs_set_tags_bytes(self, bytes):
3706
return self._real_branch._set_tags_bytes(bytes)
3708
def _set_tags_bytes(self, bytes):
3709
if self.is_locked():
3710
self._tags_bytes = bytes
3711
medium = self._client._medium
3712
if medium._is_remote_before((1, 18)):
3713
self._vfs_set_tags_bytes(bytes)
3717
self._remote_path(), self._lock_token, self._repo_lock_token)
3718
response = self._call_with_body_bytes(
3719
b'Branch.set_tags_bytes', args, bytes)
3720
except errors.UnknownSmartMethod:
3721
medium._remember_remote_is_before((1, 18))
3722
self._vfs_set_tags_bytes(bytes)
3724
def lock_read(self):
3725
"""Lock the branch for read operations.
3727
:return: A breezy.lock.LogicalLockResult.
3729
self.repository.lock_read()
3730
if not self._lock_mode:
3731
self._note_lock('r')
3732
self._lock_mode = 'r'
3733
self._lock_count = 1
3734
if self._real_branch is not None:
3735
self._real_branch.lock_read()
3737
self._lock_count += 1
3738
return lock.LogicalLockResult(self.unlock)
3740
def _remote_lock_write(self, token):
3742
branch_token = repo_token = b''
3744
branch_token = token
3745
repo_token = self.repository.lock_write().repository_token
3746
self.repository.unlock()
3747
err_context = {'token': token}
3749
response = self._call(
3750
b'Branch.lock_write', self._remote_path(), branch_token,
3751
repo_token or b'', **err_context)
3752
except errors.LockContention as e:
3753
# The LockContention from the server doesn't have any
3754
# information about the lock_url. We re-raise LockContention
3755
# with valid lock_url.
3756
raise errors.LockContention('(remote lock)',
3757
self.repository.base.split('.bzr/')[0])
3758
if response[0] != b'ok':
3759
raise errors.UnexpectedSmartServerResponse(response)
3760
ok, branch_token, repo_token = response
3761
return branch_token, repo_token
3763
def lock_write(self, token=None):
3764
if not self._lock_mode:
3765
self._note_lock('w')
3766
# Lock the branch and repo in one remote call.
3767
remote_tokens = self._remote_lock_write(token)
3768
self._lock_token, self._repo_lock_token = remote_tokens
3769
if not self._lock_token:
3770
raise SmartProtocolError(
3771
'Remote server did not return a token!')
3772
# Tell the self.repository object that it is locked.
3773
self.repository.lock_write(
3774
self._repo_lock_token, _skip_rpc=True)
3776
if self._real_branch is not None:
3777
self._real_branch.lock_write(token=self._lock_token)
3778
if token is not None:
3779
self._leave_lock = True
3781
self._leave_lock = False
3782
self._lock_mode = 'w'
3783
self._lock_count = 1
3784
elif self._lock_mode == 'r':
3785
raise errors.ReadOnlyError(self)
3787
if token is not None:
3788
# A token was given to lock_write, and we're relocking, so
3789
# check that the given token actually matches the one we
3791
if token != self._lock_token:
3792
raise errors.TokenMismatch(token, self._lock_token)
3793
self._lock_count += 1
3794
# Re-lock the repository too.
3795
self.repository.lock_write(self._repo_lock_token)
3796
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3798
def _unlock(self, branch_token, repo_token):
3799
err_context = {'token': str((branch_token, repo_token))}
3800
response = self._call(
3801
b'Branch.unlock', self._remote_path(), branch_token,
3802
repo_token or b'', **err_context)
3803
if response == (b'ok',):
3805
raise errors.UnexpectedSmartServerResponse(response)
3807
@only_raises(errors.LockNotHeld, errors.LockBroken)
3810
self._lock_count -= 1
3811
if not self._lock_count:
3812
if self.conf_store is not None:
3813
self.conf_store.save_changes()
3814
self._clear_cached_state()
3815
mode = self._lock_mode
3816
self._lock_mode = None
3817
if self._real_branch is not None:
3818
if (not self._leave_lock and mode == 'w'
3819
and self._repo_lock_token):
3820
# If this RemoteBranch will remove the physical lock
3821
# for the repository, make sure the _real_branch
3822
# doesn't do it first. (Because the _real_branch's
3823
# repository is set to be the RemoteRepository.)
3824
self._real_branch.repository.leave_lock_in_place()
3825
self._real_branch.unlock()
3827
# Only write-locked branched need to make a remote method
3828
# call to perform the unlock.
3830
if not self._lock_token:
3831
raise AssertionError('Locked, but no token!')
3832
branch_token = self._lock_token
3833
repo_token = self._repo_lock_token
3834
self._lock_token = None
3835
self._repo_lock_token = None
3836
if not self._leave_lock:
3837
self._unlock(branch_token, repo_token)
3839
self.repository.unlock()
3841
def break_lock(self):
3843
response = self._call(
3844
b'Branch.break_lock', self._remote_path())
3845
except errors.UnknownSmartMethod:
3847
return self._real_branch.break_lock()
3848
if response != (b'ok',):
3849
raise errors.UnexpectedSmartServerResponse(response)
3851
def leave_lock_in_place(self):
3852
if not self._lock_token:
3853
raise NotImplementedError(self.leave_lock_in_place)
3854
self._leave_lock = True
3856
def dont_leave_lock_in_place(self):
3857
if not self._lock_token:
3858
raise NotImplementedError(self.dont_leave_lock_in_place)
3859
self._leave_lock = False
3861
def get_rev_id(self, revno, history=None):
3863
return _mod_revision.NULL_REVISION
3864
with self.lock_read():
3865
last_revision_info = self.last_revision_info()
3867
raise errors.RevnoOutOfBounds(
3868
revno, (0, last_revision_info[0]))
3869
ok, result = self.repository.get_rev_id_for_revno(
3870
revno, last_revision_info)
3873
missing_parent = result[1]
3874
# Either the revision named by the server is missing, or its parent
3875
# is. Call get_parent_map to determine which, so that we report a
3877
parent_map = self.repository.get_parent_map([missing_parent])
3878
if missing_parent in parent_map:
3879
missing_parent = parent_map[missing_parent]
3880
raise errors.NoSuchRevision(self, missing_parent)
3882
def _read_last_revision_info(self):
3883
response = self._call(
3884
b'Branch.last_revision_info', self._remote_path())
3885
if response[0] != b'ok':
3886
raise SmartProtocolError(
3887
'unexpected response code %s' % (response,))
3888
revno = int(response[1])
3889
last_revision = response[2]
3890
return (revno, last_revision)
3892
def _gen_revision_history(self):
3893
"""See Branch._gen_revision_history()."""
3894
if self._is_stacked:
3896
return self._real_branch._gen_revision_history()
3897
response_tuple, response_handler = self._call_expecting_body(
3898
b'Branch.revision_history', self._remote_path())
3899
if response_tuple[0] != b'ok':
3900
raise errors.UnexpectedSmartServerResponse(response_tuple)
3901
result = response_handler.read_body_bytes().split(b'\x00')
3906
def _remote_path(self):
3907
return self.controldir._path_for_remote_call(self._client)
3909
def _set_last_revision_descendant(self, revision_id, other_branch,
3910
allow_diverged=False, allow_overwrite_descendant=False):
3911
# This performs additional work to meet the hook contract; while its
3912
# undesirable, we have to synthesise the revno to call the hook, and
3913
# not calling the hook is worse as it means changes can't be prevented.
3914
# Having calculated this though, we can't just call into
3915
# set_last_revision_info as a simple call, because there is a set_rh
3916
# hook that some folk may still be using.
3917
old_revno, old_revid = self.last_revision_info()
3918
history = self._lefthand_history(revision_id)
3919
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3920
err_context = {'other_branch': other_branch}
3921
response = self._call(b'Branch.set_last_revision_ex',
3922
self._remote_path(), self._lock_token, self._repo_lock_token,
3923
revision_id, int(allow_diverged), int(
3924
allow_overwrite_descendant),
3926
self._clear_cached_state()
3927
if len(response) != 3 and response[0] != b'ok':
3928
raise errors.UnexpectedSmartServerResponse(response)
3929
new_revno, new_revision_id = response[1:]
3930
self._last_revision_info_cache = new_revno, new_revision_id
3931
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3932
if self._real_branch is not None:
3933
cache = new_revno, new_revision_id
3934
self._real_branch._last_revision_info_cache = cache
3936
def _set_last_revision(self, revision_id):
3937
old_revno, old_revid = self.last_revision_info()
3938
# This performs additional work to meet the hook contract; while its
3939
# undesirable, we have to synthesise the revno to call the hook, and
3940
# not calling the hook is worse as it means changes can't be prevented.
3941
# Having calculated this though, we can't just call into
3942
# set_last_revision_info as a simple call, because there is a set_rh
3943
# hook that some folk may still be using.
3944
history = self._lefthand_history(revision_id)
3945
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3946
self._clear_cached_state()
3947
response = self._call(b'Branch.set_last_revision',
3948
self._remote_path(), self._lock_token, self._repo_lock_token,
3950
if response != (b'ok',):
3951
raise errors.UnexpectedSmartServerResponse(response)
3952
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3954
def _get_parent_location(self):
3955
medium = self._client._medium
3956
if medium._is_remote_before((1, 13)):
3957
return self._vfs_get_parent_location()
3959
response = self._call(b'Branch.get_parent', self._remote_path())
3960
except errors.UnknownSmartMethod:
3961
medium._remember_remote_is_before((1, 13))
3962
return self._vfs_get_parent_location()
3963
if len(response) != 1:
3964
raise errors.UnexpectedSmartServerResponse(response)
3965
parent_location = response[0]
3966
if parent_location == b'':
3968
return parent_location.decode('utf-8')
3970
def _vfs_get_parent_location(self):
3972
return self._real_branch._get_parent_location()
3974
def _set_parent_location(self, url):
3975
medium = self._client._medium
3976
if medium._is_remote_before((1, 15)):
3977
return self._vfs_set_parent_location(url)
3979
call_url = url or u''
3980
if isinstance(call_url, str):
3981
call_url = call_url.encode('utf-8')
3982
response = self._call(b'Branch.set_parent_location',
3983
self._remote_path(), self._lock_token, self._repo_lock_token,
3985
except errors.UnknownSmartMethod:
3986
medium._remember_remote_is_before((1, 15))
3987
return self._vfs_set_parent_location(url)
3989
raise errors.UnexpectedSmartServerResponse(response)
3991
def _vfs_set_parent_location(self, url):
3993
return self._real_branch._set_parent_location(url)
3995
def pull(self, source, overwrite=False, stop_revision=None,
3997
with self.lock_write():
3998
self._clear_cached_state_of_remote_branch_only()
4000
return self._real_branch.pull(
4001
source, overwrite=overwrite, stop_revision=stop_revision,
4002
_override_hook_target=self, **kwargs)
4004
def push(self, target, overwrite=False, stop_revision=None, lossy=False, tag_selector=None):
4005
with self.lock_read():
4007
return self._real_branch.push(
4008
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
4009
_override_hook_source_branch=self, tag_selector=tag_selector)
4011
def peek_lock_mode(self):
4012
return self._lock_mode
4014
def is_locked(self):
4015
return self._lock_count >= 1
4017
def revision_id_to_dotted_revno(self, revision_id):
4018
"""Given a revision id, return its dotted revno.
4020
:return: a tuple like (1,) or (400,1,3).
4022
with self.lock_read():
4024
response = self._call(b'Branch.revision_id_to_revno',
4025
self._remote_path(), revision_id)
4026
except errors.UnknownSmartMethod:
4028
return self._real_branch.revision_id_to_dotted_revno(revision_id)
4029
except errors.UnknownErrorFromSmartServer as e:
4030
# Deal with older versions of bzr/brz that didn't explicitly
4031
# wrap GhostRevisionsHaveNoRevno.
4032
if e.error_tuple[1] == b'GhostRevisionsHaveNoRevno':
4033
(revid, ghost_revid) = re.findall(b"{([^}]+)}", e.error_tuple[2])
4034
raise errors.GhostRevisionsHaveNoRevno(
4037
if response[0] == b'ok':
4038
return tuple([int(x) for x in response[1:]])
4040
raise errors.UnexpectedSmartServerResponse(response)
4042
def revision_id_to_revno(self, revision_id):
4043
"""Given a revision id on the branch mainline, return its revno.
4047
with self.lock_read():
4049
response = self._call(b'Branch.revision_id_to_revno',
4050
self._remote_path(), revision_id)
4051
except errors.UnknownSmartMethod:
4053
return self._real_branch.revision_id_to_revno(revision_id)
4054
if response[0] == b'ok':
4055
if len(response) == 2:
4056
return int(response[1])
4057
raise NoSuchRevision(self, revision_id)
4059
raise errors.UnexpectedSmartServerResponse(response)
4061
def set_last_revision_info(self, revno, revision_id):
4062
with self.lock_write():
4063
# XXX: These should be returned by the set_last_revision_info verb
4064
old_revno, old_revid = self.last_revision_info()
4065
self._run_pre_change_branch_tip_hooks(revno, revision_id)
4066
if not revision_id or not isinstance(revision_id, bytes):
4067
raise errors.InvalidRevisionId(
4068
revision_id=revision_id, branch=self)
4070
response = self._call(b'Branch.set_last_revision_info',
4071
self._remote_path(), self._lock_token, self._repo_lock_token,
4072
str(revno).encode('ascii'), revision_id)
4073
except errors.UnknownSmartMethod:
4075
self._clear_cached_state_of_remote_branch_only()
4076
self._real_branch.set_last_revision_info(revno, revision_id)
4077
self._last_revision_info_cache = revno, revision_id
4079
if response == (b'ok',):
4080
self._clear_cached_state()
4081
self._last_revision_info_cache = revno, revision_id
4082
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
4083
# Update the _real_branch's cache too.
4084
if self._real_branch is not None:
4085
cache = self._last_revision_info_cache
4086
self._real_branch._last_revision_info_cache = cache
4088
raise errors.UnexpectedSmartServerResponse(response)
4090
def generate_revision_history(self, revision_id, last_rev=None,
4092
with self.lock_write():
4093
medium = self._client._medium
4094
if not medium._is_remote_before((1, 6)):
4095
# Use a smart method for 1.6 and above servers
4097
self._set_last_revision_descendant(revision_id, other_branch,
4098
allow_diverged=True, allow_overwrite_descendant=True)
4100
except errors.UnknownSmartMethod:
4101
medium._remember_remote_is_before((1, 6))
4102
self._clear_cached_state_of_remote_branch_only()
4103
graph = self.repository.get_graph()
4104
(last_revno, last_revid) = self.last_revision_info()
4105
known_revision_ids = [
4106
(last_revid, last_revno),
4107
(_mod_revision.NULL_REVISION, 0),
4109
if last_rev is not None:
4110
if not graph.is_ancestor(last_rev, revision_id):
4111
# our previous tip is not merged into stop_revision
4112
raise errors.DivergedBranches(self, other_branch)
4113
revno = graph.find_distance_to_null(
4114
revision_id, known_revision_ids)
4115
self.set_last_revision_info(revno, revision_id)
4117
def set_push_location(self, location):
4118
self._set_config_location('push_location', location)
4120
def heads_to_fetch(self):
4121
if self._format._use_default_local_heads_to_fetch():
4122
# We recognise this format, and its heads-to-fetch implementation
4123
# is the default one (tip + tags). In this case it's cheaper to
4124
# just use the default implementation rather than a special RPC as
4125
# the tip and tags data is cached.
4126
return branch.Branch.heads_to_fetch(self)
4127
medium = self._client._medium
4128
if medium._is_remote_before((2, 4)):
4129
return self._vfs_heads_to_fetch()
4131
return self._rpc_heads_to_fetch()
4132
except errors.UnknownSmartMethod:
4133
medium._remember_remote_is_before((2, 4))
4134
return self._vfs_heads_to_fetch()
4136
def _rpc_heads_to_fetch(self):
4137
response = self._call(b'Branch.heads_to_fetch', self._remote_path())
4138
if len(response) != 2:
4139
raise errors.UnexpectedSmartServerResponse(response)
4140
must_fetch, if_present_fetch = response
4141
return set(must_fetch), set(if_present_fetch)
4143
def _vfs_heads_to_fetch(self):
4145
return self._real_branch.heads_to_fetch()
4147
def reconcile(self, thorough=True):
4148
"""Make sure the data stored in this branch is consistent."""
4149
from .reconcile import BranchReconciler
4150
with self.lock_write():
4151
reconciler = BranchReconciler(self, thorough=thorough)
4152
return reconciler.reconcile()
4154
def get_reference_info(self, file_id):
4155
"""Get the tree_path and branch_location for a tree reference."""
4156
if not self._format.supports_reference_locations:
4157
raise errors.UnsupportedOperation(self.get_reference_info, self)
4158
return self._get_all_reference_info().get(file_id, (None, None))
4160
def set_reference_info(self, file_id, branch_location, tree_path=None):
4161
"""Set the branch location to use for a tree reference."""
4162
if not self._format.supports_reference_locations:
4163
raise errors.UnsupportedOperation(self.set_reference_info, self)
4165
self._real_branch.set_reference_info(
4166
file_id, branch_location, tree_path)
4168
def _set_all_reference_info(self, reference_info):
4169
if not self._format.supports_reference_locations:
4170
raise errors.UnsupportedOperation(self.set_reference_info, self)
4172
self._real_branch._set_all_reference_info(reference_info)
4174
def _get_all_reference_info(self):
4175
if not self._format.supports_reference_locations:
4178
response, handler = self._call_expecting_body(
4179
b'Branch.get_all_reference_info', self._remote_path())
4180
except errors.UnknownSmartMethod:
4182
return self._real_branch._get_all_reference_info()
4183
if len(response) and response[0] != b'ok':
4184
raise errors.UnexpectedSmartServerResponse(response)
4186
for (f, u, p) in bencode.bdecode(handler.read_body_bytes()):
4187
ret[f] = (u.decode('utf-8'), p.decode('utf-8') if p else None)
4190
def reference_parent(self, file_id, path, possible_transports=None):
4191
"""Return the parent branch for a tree-reference.
4193
:param path: The path of the nested tree in the tree
4194
:return: A branch associated with the nested tree
4196
branch_location = self.get_reference_info(file_id)[0]
4197
if branch_location is None:
4199
return branch.Branch.open_from_transport(
4200
self.controldir.root_transport.clone(path),
4201
possible_transports=possible_transports)
4202
except errors.NotBranchError:
4204
return branch.Branch.open(
4206
urlutils.strip_segment_parameters(self.user_url), branch_location),
4207
possible_transports=possible_transports)
4210
class RemoteConfig(object):
4211
"""A Config that reads and writes from smart verbs.
4213
It is a low-level object that considers config data to be name/value pairs
4214
that may be associated with a section. Assigning meaning to the these
4215
values is done at higher levels like breezy.config.TreeConfig.
4218
def get_option(self, name, section=None, default=None):
4219
"""Return the value associated with a named option.
4221
:param name: The name of the value
4222
:param section: The section the option is in (if any)
4223
:param default: The value to return if the value is not set
4224
:return: The value or default value
4227
configobj = self._get_configobj()
4230
section_obj = configobj
4233
section_obj = configobj[section]
4236
if section_obj is None:
4239
value = section_obj.get(name, default)
4240
except errors.UnknownSmartMethod:
4241
value = self._vfs_get_option(name, section, default)
4242
for hook in _mod_config.OldConfigHooks['get']:
4243
hook(self, name, value)
4246
def _response_to_configobj(self, response):
4247
if len(response[0]) and response[0][0] != b'ok':
4248
raise errors.UnexpectedSmartServerResponse(response)
4249
lines = response[1].read_body_bytes().splitlines()
4250
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4251
for hook in _mod_config.OldConfigHooks['load']:
4256
class RemoteBranchConfig(RemoteConfig):
4257
"""A RemoteConfig for Branches."""
4259
def __init__(self, branch):
4260
self._branch = branch
4262
def _get_configobj(self):
4263
path = self._branch._remote_path()
4264
response = self._branch._client.call_expecting_body(
4265
b'Branch.get_config_file', path)
4266
return self._response_to_configobj(response)
4268
def set_option(self, value, name, section=None):
4269
"""Set the value associated with a named option.
4271
:param value: The value to set
4272
:param name: The name of the value to set
4273
:param section: The section the option is in (if any)
4275
medium = self._branch._client._medium
4276
if medium._is_remote_before((1, 14)):
4277
return self._vfs_set_option(value, name, section)
4278
if isinstance(value, dict):
4279
if medium._is_remote_before((2, 2)):
4280
return self._vfs_set_option(value, name, section)
4281
return self._set_config_option_dict(value, name, section)
4283
return self._set_config_option(value, name, section)
4285
def _set_config_option(self, value, name, section):
4286
if isinstance(value, (bool, int)):
4288
elif isinstance(value, str):
4291
raise TypeError(value)
4293
path = self._branch._remote_path()
4294
response = self._branch._client.call(b'Branch.set_config_option',
4295
path, self._branch._lock_token, self._branch._repo_lock_token,
4296
value.encode('utf-8'), name.encode('utf-8'),
4297
(section or '').encode('utf-8'))
4298
except errors.UnknownSmartMethod:
4299
medium = self._branch._client._medium
4300
medium._remember_remote_is_before((1, 14))
4301
return self._vfs_set_option(value, name, section)
4303
raise errors.UnexpectedSmartServerResponse(response)
4305
def _serialize_option_dict(self, option_dict):
4307
for key, value in option_dict.items():
4308
if isinstance(key, str):
4309
key = key.encode('utf8')
4310
if isinstance(value, str):
4311
value = value.encode('utf8')
4312
utf8_dict[key] = value
4313
return bencode.bencode(utf8_dict)
4315
def _set_config_option_dict(self, value, name, section):
4317
path = self._branch._remote_path()
4318
serialised_dict = self._serialize_option_dict(value)
4319
response = self._branch._client.call(
4320
b'Branch.set_config_option_dict',
4321
path, self._branch._lock_token, self._branch._repo_lock_token,
4322
serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
4323
except errors.UnknownSmartMethod:
4324
medium = self._branch._client._medium
4325
medium._remember_remote_is_before((2, 2))
4326
return self._vfs_set_option(value, name, section)
4328
raise errors.UnexpectedSmartServerResponse(response)
4330
def _real_object(self):
4331
self._branch._ensure_real()
4332
return self._branch._real_branch
4334
def _vfs_set_option(self, value, name, section=None):
4335
return self._real_object()._get_config().set_option(
4336
value, name, section)
4339
class RemoteBzrDirConfig(RemoteConfig):
4340
"""A RemoteConfig for BzrDirs."""
4342
def __init__(self, bzrdir):
4343
self._bzrdir = bzrdir
4345
def _get_configobj(self):
4346
medium = self._bzrdir._client._medium
4347
verb = b'BzrDir.get_config_file'
4348
if medium._is_remote_before((1, 15)):
4349
raise errors.UnknownSmartMethod(verb)
4350
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4351
response = self._bzrdir._call_expecting_body(
4353
return self._response_to_configobj(response)
4355
def _vfs_get_option(self, name, section, default):
4356
return self._real_object()._get_config().get_option(
4357
name, section, default)
4359
def set_option(self, value, name, section=None):
4360
"""Set the value associated with a named option.
4362
:param value: The value to set
4363
:param name: The name of the value to set
4364
:param section: The section the option is in (if any)
4366
return self._real_object()._get_config().set_option(
4367
value, name, section)
4369
def _real_object(self):
4370
self._bzrdir._ensure_real()
4371
return self._bzrdir._real_bzrdir
4374
error_translators = registry.Registry()
4375
no_context_error_translators = registry.Registry()
4378
def _translate_error(err, **context):
4379
"""Translate an ErrorFromSmartServer into a more useful error.
4381
Possible context keys:
4389
If the error from the server doesn't match a known pattern, then
4390
UnknownErrorFromSmartServer is raised.
4394
return context[name]
4396
mutter('Missing key \'%s\' in context %r', name, context)
4400
"""Get the path from the context if present, otherwise use first error
4404
return context['path']
4407
return err.error_args[0].decode('utf-8')
4409
mutter('Missing key \'path\' in context %r', context)
4411
if not isinstance(err.error_verb, bytes):
4412
raise TypeError(err.error_verb)
4414
translator = error_translators.get(err.error_verb)
4418
raise translator(err, find, get_path)
4420
translator = no_context_error_translators.get(err.error_verb)
4422
raise errors.UnknownErrorFromSmartServer(err)
4424
raise translator(err)
4427
error_translators.register(b'NoSuchRevision',
4428
lambda err, find, get_path: NoSuchRevision(
4429
find('branch'), err.error_args[0]))
4430
error_translators.register(b'nosuchrevision',
4431
lambda err, find, get_path: NoSuchRevision(
4432
find('repository'), err.error_args[0]))
4433
error_translators.register(
4434
b'revno-outofbounds',
4435
lambda err, find, get_path: errors.RevnoOutOfBounds(
4436
err.error_args[0], (err.error_args[1], err.error_args[2])))
4439
def _translate_nobranch_error(err, find, get_path):
4440
if len(err.error_args) >= 1:
4441
extra = err.error_args[0].decode('utf-8')
4444
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4448
error_translators.register(b'nobranch', _translate_nobranch_error)
4449
error_translators.register(b'norepository',
4450
lambda err, find, get_path: errors.NoRepositoryPresent(
4452
error_translators.register(b'UnlockableTransport',
4453
lambda err, find, get_path: errors.UnlockableTransport(
4454
find('bzrdir').root_transport))
4455
error_translators.register(b'TokenMismatch',
4456
lambda err, find, get_path: errors.TokenMismatch(
4457
find('token'), '(remote token)'))
4458
error_translators.register(b'Diverged',
4459
lambda err, find, get_path: errors.DivergedBranches(
4460
find('branch'), find('other_branch')))
4461
error_translators.register(b'NotStacked',
4462
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4465
def _translate_PermissionDenied(err, find, get_path):
4467
if len(err.error_args) >= 2:
4468
extra = err.error_args[1].decode('utf-8')
4471
return errors.PermissionDenied(path, extra=extra)
4474
error_translators.register(b'PermissionDenied', _translate_PermissionDenied)
4475
error_translators.register(b'ReadError',
4476
lambda err, find, get_path: errors.ReadError(get_path()))
4477
error_translators.register(b'NoSuchFile',
4478
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4479
error_translators.register(b'TokenLockingNotSupported',
4480
lambda err, find, get_path: errors.TokenLockingNotSupported(
4481
find('repository')))
4482
error_translators.register(b'UnsuspendableWriteGroup',
4483
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4484
repository=find('repository')))
4485
error_translators.register(b'UnresumableWriteGroup',
4486
lambda err, find, get_path: errors.UnresumableWriteGroup(
4487
repository=find('repository'), write_groups=err.error_args[0],
4488
reason=err.error_args[1]))
4489
error_translators.register(b'AlreadyControlDir',
4490
lambda err, find, get_path: errors.AlreadyControlDirError(get_path()))
4492
no_context_error_translators.register(b'GhostRevisionsHaveNoRevno',
4493
lambda err: errors.GhostRevisionsHaveNoRevno(*err.error_args))
4494
no_context_error_translators.register(b'IncompatibleRepositories',
4495
lambda err: errors.IncompatibleRepositories(
4496
err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8'), err.error_args[2].decode('utf-8')))
4497
no_context_error_translators.register(b'LockContention',
4498
lambda err: errors.LockContention('(remote lock)'))
4499
no_context_error_translators.register(b'LockFailed',
4500
lambda err: errors.LockFailed(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4501
no_context_error_translators.register(b'TipChangeRejected',
4502
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4503
no_context_error_translators.register(b'UnstackableBranchFormat',
4504
lambda err: branch.UnstackableBranchFormat(*err.error_args))
4505
no_context_error_translators.register(b'UnstackableRepositoryFormat',
4506
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4507
no_context_error_translators.register(b'FileExists',
4508
lambda err: errors.FileExists(err.error_args[0].decode('utf-8')))
4509
no_context_error_translators.register(b'DirectoryNotEmpty',
4510
lambda err: errors.DirectoryNotEmpty(err.error_args[0].decode('utf-8')))
4511
no_context_error_translators.register(b'UnknownFormat',
4512
lambda err: errors.UnknownFormatError(
4513
err.error_args[0].decode('ascii'), err.error_args[0].decode('ascii')))
4514
no_context_error_translators.register(b'InvalidURL',
4515
lambda err: urlutils.InvalidURL(
4516
err.error_args[0].decode('utf-8'), err.error_args[1].decode('ascii')))
4519
def _translate_short_readv_error(err):
4520
args = err.error_args
4521
return errors.ShortReadvError(
4522
args[0].decode('utf-8'),
4523
int(args[1].decode('ascii')), int(args[2].decode('ascii')),
4524
int(args[3].decode('ascii')))
4527
no_context_error_translators.register(b'ShortReadvError',
4528
_translate_short_readv_error)
4531
def _translate_unicode_error(err):
4532
encoding = err.error_args[0].decode('ascii')
4533
val = err.error_args[1].decode('utf-8')
4534
start = int(err.error_args[2].decode('ascii'))
4535
end = int(err.error_args[3].decode('ascii'))
4536
reason = err.error_args[4].decode('utf-8')
4537
if val.startswith('u:'):
4538
val = val[2:].decode('utf-8')
4539
elif val.startswith('s:'):
4540
val = val[2:].decode('base64')
4541
if err.error_verb == 'UnicodeDecodeError':
4542
raise UnicodeDecodeError(encoding, val, start, end, reason)
4543
elif err.error_verb == 'UnicodeEncodeError':
4544
raise UnicodeEncodeError(encoding, val, start, end, reason)
4547
no_context_error_translators.register(b'UnicodeEncodeError',
4548
_translate_unicode_error)
4549
no_context_error_translators.register(b'UnicodeDecodeError',
4550
_translate_unicode_error)
4551
no_context_error_translators.register(b'ReadOnlyError',
4552
lambda err: errors.TransportNotPossible('readonly transport'))
4553
no_context_error_translators.register(b'MemoryError',
4554
lambda err: errors.BzrError("remote server out of memory\n"
4555
"Retry non-remotely, or contact the server admin for details."))
4556
no_context_error_translators.register(b'RevisionNotPresent',
4557
lambda err: errors.RevisionNotPresent(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4559
no_context_error_translators.register(b'BzrCheckError',
4560
lambda err: errors.BzrCheckError(msg=err.error_args[0].decode('utf-8')))