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
17
from __future__ import absolute_import
29
config as _mod_config,
39
repository as _mod_repository,
40
revision as _mod_revision,
45
bzrdir as _mod_bzrdir,
47
testament as _mod_testament,
51
from .branch import BranchReferenceFormat
52
from ..branch import BranchWriteLockResult
53
from ..decorators import only_raises
54
from ..errors import (
58
from ..i18n import gettext
59
from .inventory import Inventory
60
from .inventorytree import InventoryRevisionTree
61
from ..lockable_files import LockableFiles
62
from ..sixish import (
69
from .smart import client, vfs, repository as smart_repo
70
from .smart.client import _SmartClient
71
from ..revision import NULL_REVISION
72
from ..repository import RepositoryWriteLockResult, _LazyListJoin
73
from .serializer import format_registry as serializer_format_registry
74
from ..trace import mutter, note, warning, log_exception_quietly
75
from .versionedfile import FulltextContentFactory
78
_DEFAULT_SEARCH_DEPTH = 100
81
class _RpcHelper(object):
82
"""Mixin class that helps with issuing RPCs."""
84
def _call(self, method, *args, **err_context):
86
return self._client.call(method, *args)
87
except errors.ErrorFromSmartServer as err:
88
self._translate_error(err, **err_context)
90
def _call_expecting_body(self, method, *args, **err_context):
92
return self._client.call_expecting_body(method, *args)
93
except errors.ErrorFromSmartServer as err:
94
self._translate_error(err, **err_context)
96
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
98
return self._client.call_with_body_bytes(method, args, body_bytes)
99
except errors.ErrorFromSmartServer as err:
100
self._translate_error(err, **err_context)
102
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
105
return self._client.call_with_body_bytes_expecting_body(
106
method, args, body_bytes)
107
except errors.ErrorFromSmartServer as err:
108
self._translate_error(err, **err_context)
111
def response_tuple_to_repo_format(response):
112
"""Convert a response tuple describing a repository format to a format."""
113
format = RemoteRepositoryFormat()
114
format._rich_root_data = (response[0] == b'yes')
115
format._supports_tree_reference = (response[1] == b'yes')
116
format._supports_external_lookups = (response[2] == b'yes')
117
format._network_name = response[3]
121
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.bzr.remote
122
# does not have to be imported unless a remote format is involved.
124
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
125
"""Format representing bzrdirs accessed via a smart server"""
127
supports_workingtrees = False
129
colocated_branches = False
132
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
133
# XXX: It's a bit ugly that the network name is here, because we'd
134
# like to believe that format objects are stateless or at least
135
# immutable, However, we do at least avoid mutating the name after
136
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
137
self._network_name = None
140
return "%s(_network_name=%r)" % (self.__class__.__name__,
143
def get_format_description(self):
144
if self._network_name:
146
real_format = controldir.network_format_registry.get(
151
return 'Remote: ' + real_format.get_format_description()
152
return 'bzr remote bzrdir'
154
def get_format_string(self):
155
raise NotImplementedError(self.get_format_string)
157
def network_name(self):
158
if self._network_name:
159
return self._network_name
161
raise AssertionError("No network name set.")
163
def initialize_on_transport(self, transport):
165
# hand off the request to the smart server
166
client_medium = transport.get_smart_medium()
167
except errors.NoSmartMedium:
168
# TODO: lookup the local format from a server hint.
169
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
170
return local_dir_format.initialize_on_transport(transport)
171
client = _SmartClient(client_medium)
172
path = client.remote_path_from_transport(transport)
174
response = client.call(b'BzrDirFormat.initialize', path)
175
except errors.ErrorFromSmartServer as err:
176
_translate_error(err, path=path)
177
if response[0] != b'ok':
178
raise errors.SmartProtocolError(
179
'unexpected response code %s' % (response,))
180
format = RemoteBzrDirFormat()
181
self._supply_sub_formats_to(format)
182
return RemoteBzrDir(transport, format)
184
def parse_NoneTrueFalse(self, arg):
191
raise AssertionError("invalid arg %r" % arg)
193
def _serialize_NoneTrueFalse(self, arg):
200
def _serialize_NoneString(self, arg):
203
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
204
create_prefix=False, force_new_repo=False, stacked_on=None,
205
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
208
# hand off the request to the smart server
209
client_medium = transport.get_smart_medium()
210
except errors.NoSmartMedium:
213
# Decline to open it if the server doesn't support our required
214
# version (3) so that the VFS-based transport will do it.
215
if client_medium.should_probe():
217
server_version = client_medium.protocol_version()
218
if server_version != '2':
222
except errors.SmartProtocolError:
223
# Apparently there's no usable smart server there, even though
224
# the medium supports the smart protocol.
229
client = _SmartClient(client_medium)
230
path = client.remote_path_from_transport(transport)
231
if client_medium._is_remote_before((1, 16)):
234
# TODO: lookup the local format from a server hint.
235
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
236
self._supply_sub_formats_to(local_dir_format)
237
return local_dir_format.initialize_on_transport_ex(transport,
238
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
239
force_new_repo=force_new_repo, stacked_on=stacked_on,
240
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
241
make_working_trees=make_working_trees, shared_repo=shared_repo,
243
return self._initialize_on_transport_ex_rpc(client, path, transport,
244
use_existing_dir, create_prefix, force_new_repo, stacked_on,
245
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
247
def _initialize_on_transport_ex_rpc(self, client, path, transport,
248
use_existing_dir, create_prefix, force_new_repo, stacked_on,
249
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
251
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
252
args.append(self._serialize_NoneTrueFalse(create_prefix))
253
args.append(self._serialize_NoneTrueFalse(force_new_repo))
254
args.append(self._serialize_NoneString(stacked_on))
255
# stack_on_pwd is often/usually our transport
258
stack_on_pwd = transport.relpath(stack_on_pwd).encode('utf-8')
261
except errors.PathNotChild:
263
args.append(self._serialize_NoneString(stack_on_pwd))
264
args.append(self._serialize_NoneString(repo_format_name))
265
args.append(self._serialize_NoneTrueFalse(make_working_trees))
266
args.append(self._serialize_NoneTrueFalse(shared_repo))
267
request_network_name = self._network_name or \
268
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
270
response = client.call(b'BzrDirFormat.initialize_ex_1.16',
271
request_network_name, path, *args)
272
except errors.UnknownSmartMethod:
273
client._medium._remember_remote_is_before((1, 16))
274
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
275
self._supply_sub_formats_to(local_dir_format)
276
return local_dir_format.initialize_on_transport_ex(transport,
277
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
278
force_new_repo=force_new_repo, stacked_on=stacked_on,
279
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
280
make_working_trees=make_working_trees, shared_repo=shared_repo,
282
except errors.ErrorFromSmartServer as err:
283
_translate_error(err, path=path.decode('utf-8'))
284
repo_path = response[0]
285
bzrdir_name = response[6]
286
require_stacking = response[7]
287
require_stacking = self.parse_NoneTrueFalse(require_stacking)
288
format = RemoteBzrDirFormat()
289
format._network_name = bzrdir_name
290
self._supply_sub_formats_to(format)
291
bzrdir = RemoteBzrDir(transport, format, _client=client)
293
repo_format = response_tuple_to_repo_format(response[1:])
294
if repo_path == b'.':
296
repo_path = repo_path.decode('utf-8')
298
repo_bzrdir_format = RemoteBzrDirFormat()
299
repo_bzrdir_format._network_name = response[5]
300
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
304
final_stack = response[8] or None
306
final_stack = final_stack.decode('utf-8')
307
final_stack_pwd = response[9] or None
309
final_stack_pwd = urlutils.join(
310
transport.base, final_stack_pwd.decode('utf-8'))
311
remote_repo = RemoteRepository(repo_bzr, repo_format)
312
if len(response) > 10:
313
# Updated server verb that locks remotely.
314
repo_lock_token = response[10] or None
315
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
317
remote_repo.dont_leave_lock_in_place()
319
remote_repo.lock_write()
320
policy = _mod_bzrdir.UseExistingRepository(remote_repo,
321
final_stack, final_stack_pwd, require_stacking)
322
policy.acquire_repository()
326
bzrdir._format.set_branch_format(self.get_branch_format())
328
# The repo has already been created, but we need to make sure that
329
# we'll make a stackable branch.
330
bzrdir._format.require_stacking(_skip_repo=True)
331
return remote_repo, bzrdir, require_stacking, policy
333
def _open(self, transport):
334
return RemoteBzrDir(transport, self)
336
def __eq__(self, other):
337
if not isinstance(other, RemoteBzrDirFormat):
339
return self.get_format_description() == other.get_format_description()
341
def __return_repository_format(self):
342
# Always return a RemoteRepositoryFormat object, but if a specific bzr
343
# repository format has been asked for, tell the RemoteRepositoryFormat
344
# that it should use that for init() etc.
345
result = RemoteRepositoryFormat()
346
custom_format = getattr(self, '_repository_format', None)
348
if isinstance(custom_format, RemoteRepositoryFormat):
351
# We will use the custom format to create repositories over the
352
# wire; expose its details like rich_root_data for code to
354
result._custom_format = custom_format
357
def get_branch_format(self):
358
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
359
if not isinstance(result, RemoteBranchFormat):
360
new_result = RemoteBranchFormat()
361
new_result._custom_format = result
363
self.set_branch_format(new_result)
367
repository_format = property(__return_repository_format,
368
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) # .im_func)
371
class RemoteControlStore(_mod_config.IniFileStore):
372
"""Control store which attempts to use HPSS calls to retrieve control store.
374
Note that this is specific to bzr-based formats.
377
def __init__(self, bzrdir):
378
super(RemoteControlStore, self).__init__()
379
self.controldir = bzrdir
380
self._real_store = None
382
def lock_write(self, token=None):
384
return self._real_store.lock_write(token)
388
return self._real_store.unlock()
391
with self.lock_write():
392
# We need to be able to override the undecorated implementation
393
self.save_without_locking()
395
def save_without_locking(self):
396
super(RemoteControlStore, self).save()
398
def _ensure_real(self):
399
self.controldir._ensure_real()
400
if self._real_store is None:
401
self._real_store = _mod_config.ControlStore(self.controldir)
403
def external_url(self):
404
return urlutils.join(self.branch.user_url, 'control.conf')
406
def _load_content(self):
407
medium = self.controldir._client._medium
408
path = self.controldir._path_for_remote_call(self.controldir._client)
410
response, handler = self.controldir._call_expecting_body(
411
b'BzrDir.get_config_file', path)
412
except errors.UnknownSmartMethod:
414
return self._real_store._load_content()
415
if len(response) and response[0] != b'ok':
416
raise errors.UnexpectedSmartServerResponse(response)
417
return handler.read_body_bytes()
419
def _save_content(self, content):
420
# FIXME JRV 2011-11-22: Ideally this should use a
421
# HPSS call too, but at the moment it is not possible
422
# to write lock control directories.
424
return self._real_store._save_content(content)
427
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
428
"""Control directory on a remote server, accessed via bzr:// or similar."""
430
def __init__(self, transport, format, _client=None, _force_probe=False):
431
"""Construct a RemoteBzrDir.
433
:param _client: Private parameter for testing. Disables probing and the
434
use of a real bzrdir.
436
_mod_bzrdir.BzrDir.__init__(self, transport, format)
437
# this object holds a delegated bzrdir that uses file-level operations
438
# to talk to the other side
439
self._real_bzrdir = None
440
self._has_working_tree = None
441
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
442
# create_branch for details.
443
self._next_open_branch_result = None
446
medium = transport.get_smart_medium()
447
self._client = client._SmartClient(medium)
449
self._client = _client
456
return '%s(%r)' % (self.__class__.__name__, self._client)
458
def _probe_bzrdir(self):
459
medium = self._client._medium
460
path = self._path_for_remote_call(self._client)
461
if medium._is_remote_before((2, 1)):
465
self._rpc_open_2_1(path)
467
except errors.UnknownSmartMethod:
468
medium._remember_remote_is_before((2, 1))
471
def _rpc_open_2_1(self, path):
472
response = self._call(b'BzrDir.open_2.1', path)
473
if response == (b'no',):
474
raise errors.NotBranchError(path=self.root_transport.base)
475
elif response[0] == b'yes':
476
if response[1] == b'yes':
477
self._has_working_tree = True
478
elif response[1] == b'no':
479
self._has_working_tree = False
481
raise errors.UnexpectedSmartServerResponse(response)
483
raise errors.UnexpectedSmartServerResponse(response)
485
def _rpc_open(self, path):
486
response = self._call(b'BzrDir.open', path)
487
if response not in [(b'yes',), (b'no',)]:
488
raise errors.UnexpectedSmartServerResponse(response)
489
if response == (b'no',):
490
raise errors.NotBranchError(path=self.root_transport.base)
492
def _ensure_real(self):
493
"""Ensure that there is a _real_bzrdir set.
495
Used before calls to self._real_bzrdir.
497
if not self._real_bzrdir:
498
if 'hpssvfs' in debug.debug_flags:
500
warning('VFS BzrDir access triggered\n%s',
501
''.join(traceback.format_stack()))
502
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
503
self.root_transport, probers=[_mod_bzr.BzrProber])
504
self._format._network_name = \
505
self._real_bzrdir._format.network_name()
507
def _translate_error(self, err, **context):
508
_translate_error(err, bzrdir=self, **context)
510
def break_lock(self):
511
# Prevent aliasing problems in the next_open_branch_result cache.
512
# See create_branch for rationale.
513
self._next_open_branch_result = None
514
return _mod_bzrdir.BzrDir.break_lock(self)
516
def _vfs_checkout_metadir(self):
518
return self._real_bzrdir.checkout_metadir()
520
def checkout_metadir(self):
521
"""Retrieve the controldir format to use for checkouts of this one.
523
medium = self._client._medium
524
if medium._is_remote_before((2, 5)):
525
return self._vfs_checkout_metadir()
526
path = self._path_for_remote_call(self._client)
528
response = self._client.call(b'BzrDir.checkout_metadir',
530
except errors.UnknownSmartMethod:
531
medium._remember_remote_is_before((2, 5))
532
return self._vfs_checkout_metadir()
533
if len(response) != 3:
534
raise errors.UnexpectedSmartServerResponse(response)
535
control_name, repo_name, branch_name = response
537
format = controldir.network_format_registry.get(control_name)
539
raise errors.UnknownFormatError(kind='control',
543
repo_format = _mod_repository.network_format_registry.get(
546
raise errors.UnknownFormatError(kind='repository',
548
format.repository_format = repo_format
551
format.set_branch_format(
552
branch.network_format_registry.get(branch_name))
554
raise errors.UnknownFormatError(kind='branch',
558
def _vfs_cloning_metadir(self, require_stacking=False):
560
return self._real_bzrdir.cloning_metadir(
561
require_stacking=require_stacking)
563
def cloning_metadir(self, require_stacking=False):
564
medium = self._client._medium
565
if medium._is_remote_before((1, 13)):
566
return self._vfs_cloning_metadir(require_stacking=require_stacking)
567
verb = b'BzrDir.cloning_metadir'
572
path = self._path_for_remote_call(self._client)
574
response = self._call(verb, path, stacking)
575
except errors.UnknownSmartMethod:
576
medium._remember_remote_is_before((1, 13))
577
return self._vfs_cloning_metadir(require_stacking=require_stacking)
578
except errors.UnknownErrorFromSmartServer as err:
579
if err.error_tuple != (b'BranchReference',):
581
# We need to resolve the branch reference to determine the
582
# cloning_metadir. This causes unnecessary RPCs to open the
583
# referenced branch (and bzrdir, etc) but only when the caller
584
# didn't already resolve the branch reference.
585
referenced_branch = self.open_branch()
586
return referenced_branch.controldir.cloning_metadir()
587
if len(response) != 3:
588
raise errors.UnexpectedSmartServerResponse(response)
589
control_name, repo_name, branch_info = response
590
if len(branch_info) != 2:
591
raise errors.UnexpectedSmartServerResponse(response)
592
branch_ref, branch_name = branch_info
594
format = controldir.network_format_registry.get(control_name)
596
raise errors.UnknownFormatError(
597
kind='control', format=control_name)
601
format.repository_format = _mod_repository.network_format_registry.get(
604
raise errors.UnknownFormatError(kind='repository',
606
if branch_ref == b'ref':
607
# XXX: we need possible_transports here to avoid reopening the
608
# connection to the referenced location
609
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
610
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
611
format.set_branch_format(branch_format)
612
elif branch_ref == b'branch':
615
branch_format = branch.network_format_registry.get(
618
raise errors.UnknownFormatError(kind='branch',
620
format.set_branch_format(branch_format)
622
raise errors.UnexpectedSmartServerResponse(response)
625
def create_repository(self, shared=False):
626
# as per meta1 formats - just delegate to the format object which may
628
result = self._format.repository_format.initialize(self, shared)
629
if not isinstance(result, RemoteRepository):
630
return self.open_repository()
634
def destroy_repository(self):
635
"""See BzrDir.destroy_repository"""
636
path = self._path_for_remote_call(self._client)
638
response = self._call(b'BzrDir.destroy_repository', path)
639
except errors.UnknownSmartMethod:
641
self._real_bzrdir.destroy_repository()
643
if response[0] != b'ok':
644
raise SmartProtocolError(
645
'unexpected response code %s' % (response,))
647
def create_branch(self, name=None, repository=None,
648
append_revisions_only=None):
650
name = self._get_selected_branch()
652
raise errors.NoColocatedBranchSupport(self)
653
# as per meta1 formats - just delegate to the format object which may
655
real_branch = self._format.get_branch_format().initialize(self,
656
name=name, repository=repository,
657
append_revisions_only=append_revisions_only)
658
if not isinstance(real_branch, RemoteBranch):
659
if not isinstance(repository, RemoteRepository):
660
raise AssertionError(
661
'need a RemoteRepository to use with RemoteBranch, got %r'
663
result = RemoteBranch(self, repository, real_branch, name=name)
666
# BzrDir.clone_on_transport() uses the result of create_branch but does
667
# not return it to its callers; we save approximately 8% of our round
668
# trips by handing the branch we created back to the first caller to
669
# open_branch rather than probing anew. Long term we need a API in
670
# bzrdir that doesn't discard result objects (like result_branch).
672
self._next_open_branch_result = result
675
def destroy_branch(self, name=None):
676
"""See BzrDir.destroy_branch"""
678
name = self._get_selected_branch()
680
raise errors.NoColocatedBranchSupport(self)
681
path = self._path_for_remote_call(self._client)
687
response = self._call(b'BzrDir.destroy_branch', path, *args)
688
except errors.UnknownSmartMethod:
690
self._real_bzrdir.destroy_branch(name=name)
691
self._next_open_branch_result = None
693
self._next_open_branch_result = None
694
if response[0] != b'ok':
695
raise SmartProtocolError(
696
'unexpected response code %s' % (response,))
698
def create_workingtree(self, revision_id=None, from_branch=None,
699
accelerator_tree=None, hardlink=False):
700
raise errors.NotLocalUrl(self.transport.base)
702
def find_branch_format(self, name=None):
703
"""Find the branch 'format' for this bzrdir.
705
This might be a synthetic object for e.g. RemoteBranch and SVN.
707
b = self.open_branch(name=name)
710
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
711
path = self._path_for_remote_call(self._client)
713
response, handler = self._call_expecting_body(
714
b'BzrDir.get_branches', path)
715
except errors.UnknownSmartMethod:
717
return self._real_bzrdir.get_branches()
718
if response[0] != b"success":
719
raise errors.UnexpectedSmartServerResponse(response)
720
body = bencode.bdecode(handler.read_body_bytes())
722
for name, value in viewitems(body):
723
name = name.decode('utf-8')
724
ret[name] = self._open_branch(name, value[0], value[1],
725
possible_transports=possible_transports,
726
ignore_fallbacks=ignore_fallbacks)
729
def set_branch_reference(self, target_branch, name=None):
730
"""See BzrDir.set_branch_reference()."""
732
name = self._get_selected_branch()
734
raise errors.NoColocatedBranchSupport(self)
736
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
738
def get_branch_reference(self, name=None):
739
"""See BzrDir.get_branch_reference()."""
741
name = self._get_selected_branch()
743
raise errors.NoColocatedBranchSupport(self)
744
response = self._get_branch_reference()
745
if response[0] == 'ref':
746
return response[1].decode('utf-8')
750
def _get_branch_reference(self):
751
"""Get branch reference information
753
:return: Tuple with (kind, location_or_format)
754
if kind == 'ref', then location_or_format contains a location
755
otherwise, it contains a format name
757
path = self._path_for_remote_call(self._client)
758
medium = self._client._medium
760
(b'BzrDir.open_branchV3', (2, 1)),
761
(b'BzrDir.open_branchV2', (1, 13)),
762
(b'BzrDir.open_branch', None),
764
for verb, required_version in candidate_calls:
765
if required_version and medium._is_remote_before(required_version):
768
response = self._call(verb, path)
769
except errors.UnknownSmartMethod:
770
if required_version is None:
772
medium._remember_remote_is_before(required_version)
775
if verb == b'BzrDir.open_branch':
776
if response[0] != b'ok':
777
raise errors.UnexpectedSmartServerResponse(response)
778
if response[1] != b'':
779
return ('ref', response[1])
781
return ('branch', b'')
782
if response[0] not in (b'ref', b'branch'):
783
raise errors.UnexpectedSmartServerResponse(response)
784
return (response[0].decode('ascii'), response[1])
786
def _get_tree_branch(self, name=None):
787
"""See BzrDir._get_tree_branch()."""
788
return None, self.open_branch(name=name)
790
def _open_branch(self, name, kind, location_or_format,
791
ignore_fallbacks=False, possible_transports=None):
793
# a branch reference, use the existing BranchReference logic.
794
format = BranchReferenceFormat()
795
return format.open(self, name=name, _found=True,
796
location=location_or_format.decode('utf-8'),
797
ignore_fallbacks=ignore_fallbacks,
798
possible_transports=possible_transports)
799
branch_format_name = location_or_format
800
if not branch_format_name:
801
branch_format_name = None
802
format = RemoteBranchFormat(network_name=branch_format_name)
803
return RemoteBranch(self, self.find_repository(), format=format,
804
setup_stacking=not ignore_fallbacks, name=name,
805
possible_transports=possible_transports)
807
def open_branch(self, name=None, unsupported=False,
808
ignore_fallbacks=False, possible_transports=None):
810
name = self._get_selected_branch()
812
raise errors.NoColocatedBranchSupport(self)
814
raise NotImplementedError(
815
'unsupported flag support not implemented yet.')
816
if self._next_open_branch_result is not None:
817
# See create_branch for details.
818
result = self._next_open_branch_result
819
self._next_open_branch_result = None
821
response = self._get_branch_reference()
822
return self._open_branch(name, response[0], response[1],
823
possible_transports=possible_transports,
824
ignore_fallbacks=ignore_fallbacks)
826
def _open_repo_v1(self, path):
827
verb = b'BzrDir.find_repository'
828
response = self._call(verb, path)
829
if response[0] != b'ok':
830
raise errors.UnexpectedSmartServerResponse(response)
831
# servers that only support the v1 method don't support external
834
repo = self._real_bzrdir.open_repository()
835
response = response + (b'no', repo._format.network_name())
836
return response, repo
838
def _open_repo_v2(self, path):
839
verb = b'BzrDir.find_repositoryV2'
840
response = self._call(verb, path)
841
if response[0] != b'ok':
842
raise errors.UnexpectedSmartServerResponse(response)
844
repo = self._real_bzrdir.open_repository()
845
response = response + (repo._format.network_name(),)
846
return response, repo
848
def _open_repo_v3(self, path):
849
verb = b'BzrDir.find_repositoryV3'
850
medium = self._client._medium
851
if medium._is_remote_before((1, 13)):
852
raise errors.UnknownSmartMethod(verb)
854
response = self._call(verb, path)
855
except errors.UnknownSmartMethod:
856
medium._remember_remote_is_before((1, 13))
858
if response[0] != b'ok':
859
raise errors.UnexpectedSmartServerResponse(response)
860
return response, None
862
def open_repository(self):
863
path = self._path_for_remote_call(self._client)
865
for probe in [self._open_repo_v3, self._open_repo_v2,
868
response, real_repo = probe(path)
870
except errors.UnknownSmartMethod:
873
raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
874
if response[0] != b'ok':
875
raise errors.UnexpectedSmartServerResponse(response)
876
if len(response) != 6:
877
raise SmartProtocolError(
878
'incorrect response length %s' % (response,))
879
if response[1] == b'':
880
# repo is at this dir.
881
format = response_tuple_to_repo_format(response[2:])
882
# Used to support creating a real format instance when needed.
883
format._creating_bzrdir = self
884
remote_repo = RemoteRepository(self, format)
885
format._creating_repo = remote_repo
886
if real_repo is not None:
887
remote_repo._set_real_repository(real_repo)
890
raise errors.NoRepositoryPresent(self)
892
def has_workingtree(self):
893
if self._has_working_tree is None:
894
path = self._path_for_remote_call(self._client)
896
response = self._call(b'BzrDir.has_workingtree', path)
897
except errors.UnknownSmartMethod:
899
self._has_working_tree = self._real_bzrdir.has_workingtree()
901
if response[0] not in (b'yes', b'no'):
902
raise SmartProtocolError(
903
'unexpected response code %s' % (response,))
904
self._has_working_tree = (response[0] == b'yes')
905
return self._has_working_tree
907
def open_workingtree(self, recommend_upgrade=True):
908
if self.has_workingtree():
909
raise errors.NotLocalUrl(self.root_transport)
911
raise errors.NoWorkingTree(self.root_transport.base)
913
def _path_for_remote_call(self, client):
914
"""Return the path to be used for this bzrdir in a remote call."""
915
remote_path = client.remote_path_from_transport(self.root_transport)
916
if sys.version_info[0] == 3:
917
remote_path = remote_path.decode('utf-8')
918
base_url, segment_parameters = urlutils.split_segment_parameters_raw(
920
if sys.version_info[0] == 3:
921
base_url = base_url.encode('utf-8')
924
def get_branch_transport(self, branch_format, name=None):
926
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
928
def get_repository_transport(self, repository_format):
930
return self._real_bzrdir.get_repository_transport(repository_format)
932
def get_workingtree_transport(self, workingtree_format):
934
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
936
def can_convert_format(self):
937
"""Upgrading of remote bzrdirs is not supported yet."""
940
def needs_format_conversion(self, format):
941
"""Upgrading of remote bzrdirs is not supported yet."""
944
def _get_config(self):
945
return RemoteBzrDirConfig(self)
947
def _get_config_store(self):
948
return RemoteControlStore(self)
951
class RemoteInventoryTree(InventoryRevisionTree):
953
def __init__(self, repository, inv, revision_id):
954
super(RemoteInventoryTree, self).__init__(repository, inv, revision_id)
956
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
957
ret = self._repository._revision_archive(
958
self.get_revision_id(), format, name, root, subdir,
959
force_mtime=force_mtime)
961
return super(RemoteInventoryTree, self).archive(
962
format, name, root, subdir, force_mtime=force_mtime)
965
def annotate_iter(self, path,
966
default_revision=_mod_revision.CURRENT_REVISION):
967
"""Return an iterator of revision_id, line tuples.
969
For working trees (and mutable trees in general), the special
970
revision_id 'current:' will be used for lines that are new in this
971
tree, e.g. uncommitted changes.
972
:param default_revision: For lines that don't match a basis, mark them
973
with this revision id. Not all implementations will make use of
976
ret = self._repository._annotate_file_revision(
977
self.get_revision_id(), path, file_id=None,
978
default_revision=default_revision)
980
return super(RemoteInventoryTree, self).annotate_iter(
981
path, default_revision=default_revision)
985
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
986
"""Format for repositories accessed over a _SmartClient.
988
Instances of this repository are represented by RemoteRepository
991
The RemoteRepositoryFormat is parameterized during construction
992
to reflect the capabilities of the real, remote format. Specifically
993
the attributes rich_root_data and supports_tree_reference are set
994
on a per instance basis, and are not set (and should not be) at
997
:ivar _custom_format: If set, a specific concrete repository format that
998
will be used when initializing a repository with this
999
RemoteRepositoryFormat.
1000
:ivar _creating_repo: If set, the repository object that this
1001
RemoteRepositoryFormat was created for: it can be called into
1002
to obtain data like the network name.
1005
_matchingcontroldir = RemoteBzrDirFormat()
1006
supports_full_versioned_files = True
1007
supports_leaving_lock = True
1008
supports_overriding_transport = False
1011
_mod_repository.RepositoryFormat.__init__(self)
1012
self._custom_format = None
1013
self._network_name = None
1014
self._creating_bzrdir = None
1015
self._revision_graph_can_have_wrong_parents = None
1016
self._supports_chks = None
1017
self._supports_external_lookups = None
1018
self._supports_tree_reference = None
1019
self._supports_funky_characters = None
1020
self._supports_nesting_repositories = None
1021
self._rich_root_data = None
1024
return "%s(_network_name=%r)" % (self.__class__.__name__,
1028
def fast_deltas(self):
1030
return self._custom_format.fast_deltas
1033
def rich_root_data(self):
1034
if self._rich_root_data is None:
1036
self._rich_root_data = self._custom_format.rich_root_data
1037
return self._rich_root_data
1040
def supports_chks(self):
1041
if self._supports_chks is None:
1043
self._supports_chks = self._custom_format.supports_chks
1044
return self._supports_chks
1047
def supports_external_lookups(self):
1048
if self._supports_external_lookups is None:
1050
self._supports_external_lookups = \
1051
self._custom_format.supports_external_lookups
1052
return self._supports_external_lookups
1055
def supports_funky_characters(self):
1056
if self._supports_funky_characters is None:
1058
self._supports_funky_characters = \
1059
self._custom_format.supports_funky_characters
1060
return self._supports_funky_characters
1063
def supports_nesting_repositories(self):
1064
if self._supports_nesting_repositories is None:
1066
self._supports_nesting_repositories = \
1067
self._custom_format.supports_nesting_repositories
1068
return self._supports_nesting_repositories
1071
def supports_tree_reference(self):
1072
if self._supports_tree_reference is None:
1074
self._supports_tree_reference = \
1075
self._custom_format.supports_tree_reference
1076
return self._supports_tree_reference
1079
def revision_graph_can_have_wrong_parents(self):
1080
if self._revision_graph_can_have_wrong_parents is None:
1082
self._revision_graph_can_have_wrong_parents = \
1083
self._custom_format.revision_graph_can_have_wrong_parents
1084
return self._revision_graph_can_have_wrong_parents
1086
def _vfs_initialize(self, a_controldir, shared):
1087
"""Helper for common code in initialize."""
1088
if self._custom_format:
1089
# Custom format requested
1090
result = self._custom_format.initialize(
1091
a_controldir, shared=shared)
1092
elif self._creating_bzrdir is not None:
1093
# Use the format that the repository we were created to back
1095
prior_repo = self._creating_bzrdir.open_repository()
1096
prior_repo._ensure_real()
1097
result = prior_repo._real_repository._format.initialize(
1098
a_controldir, shared=shared)
1100
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1101
# support remote initialization.
1102
# We delegate to a real object at this point (as RemoteBzrDir
1103
# delegate to the repository format which would lead to infinite
1104
# recursion if we just called a_controldir.create_repository.
1105
a_controldir._ensure_real()
1106
result = a_controldir._real_bzrdir.create_repository(shared=shared)
1107
if not isinstance(result, RemoteRepository):
1108
return self.open(a_controldir)
1112
def initialize(self, a_controldir, shared=False):
1113
# Being asked to create on a non RemoteBzrDir:
1114
if not isinstance(a_controldir, RemoteBzrDir):
1115
return self._vfs_initialize(a_controldir, shared)
1116
medium = a_controldir._client._medium
1117
if medium._is_remote_before((1, 13)):
1118
return self._vfs_initialize(a_controldir, shared)
1119
# Creating on a remote bzr dir.
1120
# 1) get the network name to use.
1121
if self._custom_format:
1122
network_name = self._custom_format.network_name()
1123
elif self._network_name:
1124
network_name = self._network_name
1126
# Select the current breezy default and ask for that.
1127
reference_bzrdir_format = controldir.format_registry.get(
1129
reference_format = reference_bzrdir_format.repository_format
1130
network_name = reference_format.network_name()
1131
# 2) try direct creation via RPC
1132
path = a_controldir._path_for_remote_call(a_controldir._client)
1133
verb = b'BzrDir.create_repository'
1135
shared_str = b'True'
1137
shared_str = b'False'
1139
response = a_controldir._call(verb, path, network_name, shared_str)
1140
except errors.UnknownSmartMethod:
1141
# Fallback - use vfs methods
1142
medium._remember_remote_is_before((1, 13))
1143
return self._vfs_initialize(a_controldir, shared)
1145
# Turn the response into a RemoteRepository object.
1146
format = response_tuple_to_repo_format(response[1:])
1147
# Used to support creating a real format instance when needed.
1148
format._creating_bzrdir = a_controldir
1149
remote_repo = RemoteRepository(a_controldir, format)
1150
format._creating_repo = remote_repo
1153
def open(self, a_controldir):
1154
if not isinstance(a_controldir, RemoteBzrDir):
1155
raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
1156
return a_controldir.open_repository()
1158
def _ensure_real(self):
1159
if self._custom_format is None:
1161
self._custom_format = _mod_repository.network_format_registry.get(
1164
raise errors.UnknownFormatError(kind='repository',
1165
format=self._network_name)
1168
def _fetch_order(self):
1170
return self._custom_format._fetch_order
1173
def _fetch_uses_deltas(self):
1175
return self._custom_format._fetch_uses_deltas
1178
def _fetch_reconcile(self):
1180
return self._custom_format._fetch_reconcile
1182
def get_format_description(self):
1184
return 'Remote: ' + self._custom_format.get_format_description()
1186
def __eq__(self, other):
1187
return self.__class__ is other.__class__
1189
def network_name(self):
1190
if self._network_name:
1191
return self._network_name
1192
self._creating_repo._ensure_real()
1193
return self._creating_repo._real_repository._format.network_name()
1196
def pack_compresses(self):
1198
return self._custom_format.pack_compresses
1201
def _serializer(self):
1203
return self._custom_format._serializer
1206
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1207
lock._RelockDebugMixin):
1208
"""Repository accessed over rpc.
1210
For the moment most operations are performed using local transport-backed
1214
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1215
"""Create a RemoteRepository instance.
1217
:param remote_bzrdir: The bzrdir hosting this repository.
1218
:param format: The RemoteFormat object to use.
1219
:param real_repository: If not None, a local implementation of the
1220
repository logic for the repository, usually accessing the data
1222
:param _client: Private testing parameter - override the smart client
1223
to be used by the repository.
1226
self._real_repository = real_repository
1228
self._real_repository = None
1229
self.controldir = remote_bzrdir
1231
self._client = remote_bzrdir._client
1233
self._client = _client
1234
self._format = format
1235
self._lock_mode = None
1236
self._lock_token = None
1237
self._write_group_tokens = None
1238
self._lock_count = 0
1239
self._leave_lock = False
1240
# Cache of revision parents; misses are cached during read locks, and
1241
# write locks when no _real_repository has been set.
1242
self._unstacked_provider = graph.CachingParentsProvider(
1243
get_parent_map=self._get_parent_map_rpc)
1244
self._unstacked_provider.disable_cache()
1246
# These depend on the actual remote format, so force them off for
1247
# maximum compatibility. XXX: In future these should depend on the
1248
# remote repository instance, but this is irrelevant until we perform
1249
# reconcile via an RPC call.
1250
self._reconcile_does_inventory_gc = False
1251
self._reconcile_fixes_text_parents = False
1252
self._reconcile_backsup_inventory = False
1253
self.base = self.controldir.transport.base
1254
# Additional places to query for data.
1255
self._fallback_repositories = []
1258
def user_transport(self):
1259
return self.controldir.user_transport
1262
def control_transport(self):
1263
# XXX: Normally you shouldn't directly get at the remote repository
1264
# transport, but I'm not sure it's worth making this method
1265
# optional -- mbp 2010-04-21
1266
return self.controldir.get_repository_transport(None)
1269
return "%s(%s)" % (self.__class__.__name__, self.base)
1273
def abort_write_group(self, suppress_errors=False):
1274
"""Complete a write group on the decorated repository.
1276
Smart methods perform operations in a single step so this API
1277
is not really applicable except as a compatibility thunk
1278
for older plugins that don't use e.g. the CommitBuilder
1281
:param suppress_errors: see Repository.abort_write_group.
1283
if self._real_repository:
1285
return self._real_repository.abort_write_group(
1286
suppress_errors=suppress_errors)
1287
if not self.is_in_write_group():
1289
mutter('(suppressed) not in write group')
1291
raise errors.BzrError("not in write group")
1292
path = self.controldir._path_for_remote_call(self._client)
1294
response = self._call(b'Repository.abort_write_group', path,
1296
[token.encode('utf-8') for token in self._write_group_tokens])
1297
except Exception as exc:
1298
self._write_group = None
1299
if not suppress_errors:
1301
mutter('abort_write_group failed')
1302
log_exception_quietly()
1303
note(gettext('bzr: ERROR (ignored): %s'), exc)
1305
if response != (b'ok', ):
1306
raise errors.UnexpectedSmartServerResponse(response)
1307
self._write_group_tokens = None
1310
def chk_bytes(self):
1311
"""Decorate the real repository for now.
1313
In the long term a full blown network facility is needed to avoid
1314
creating a real repository object locally.
1317
return self._real_repository.chk_bytes
1319
def commit_write_group(self):
1320
"""Complete a write group on the decorated repository.
1322
Smart methods perform operations in a single step so this API
1323
is not really applicable except as a compatibility thunk
1324
for older plugins that don't use e.g. the CommitBuilder
1327
if self._real_repository:
1329
return self._real_repository.commit_write_group()
1330
if not self.is_in_write_group():
1331
raise errors.BzrError("not in write group")
1332
path = self.controldir._path_for_remote_call(self._client)
1333
response = self._call(b'Repository.commit_write_group', path,
1334
self._lock_token, [token.encode('utf-8') for token in self._write_group_tokens])
1335
if response != (b'ok', ):
1336
raise errors.UnexpectedSmartServerResponse(response)
1337
self._write_group_tokens = None
1338
# Refresh data after writing to the repository.
1341
def resume_write_group(self, tokens):
1342
if self._real_repository:
1343
return self._real_repository.resume_write_group(tokens)
1344
path = self.controldir._path_for_remote_call(self._client)
1346
response = self._call(b'Repository.check_write_group', path,
1347
self._lock_token, [token.encode('utf-8') for token in tokens])
1348
except errors.UnknownSmartMethod:
1350
return self._real_repository.resume_write_group(tokens)
1351
if response != (b'ok', ):
1352
raise errors.UnexpectedSmartServerResponse(response)
1353
self._write_group_tokens = tokens
1355
def suspend_write_group(self):
1356
if self._real_repository:
1357
return self._real_repository.suspend_write_group()
1358
ret = self._write_group_tokens or []
1359
self._write_group_tokens = None
1362
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1364
return self._real_repository.get_missing_parent_inventories(
1365
check_for_missing_texts=check_for_missing_texts)
1367
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1369
return self._real_repository.get_rev_id_for_revno(
1372
def get_rev_id_for_revno(self, revno, known_pair):
1373
"""See Repository.get_rev_id_for_revno."""
1374
path = self.controldir._path_for_remote_call(self._client)
1376
if self._client._medium._is_remote_before((1, 17)):
1377
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1378
response = self._call(
1379
b'Repository.get_rev_id_for_revno', path, revno, known_pair)
1380
except errors.UnknownSmartMethod:
1381
self._client._medium._remember_remote_is_before((1, 17))
1382
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1383
except errors.UnknownErrorFromSmartServer as e:
1384
# Older versions of Bazaar/Breezy (<< 3.0.0) would raise a
1385
# ValueError instead of returning revno-outofbounds
1386
if len(e.error_tuple) < 3:
1388
if e.error_tuple[:2] != (b'error', b'ValueError'):
1391
b"requested revno \(([0-9]+)\) is later than given "
1392
b"known revno \(([0-9]+)\)", e.error_tuple[2])
1395
raise errors.RevnoOutOfBounds(
1396
int(m.group(1)), (0, int(m.group(2))))
1397
if response[0] == b'ok':
1398
return True, response[1]
1399
elif response[0] == b'history-incomplete':
1400
known_pair = response[1:3]
1401
for fallback in self._fallback_repositories:
1402
found, result = fallback.get_rev_id_for_revno(
1408
# Not found in any fallbacks
1409
return False, known_pair
1411
raise errors.UnexpectedSmartServerResponse(response)
1413
def _ensure_real(self):
1414
"""Ensure that there is a _real_repository set.
1416
Used before calls to self._real_repository.
1418
Note that _ensure_real causes many roundtrips to the server which are
1419
not desirable, and prevents the use of smart one-roundtrip RPC's to
1420
perform complex operations (such as accessing parent data, streaming
1421
revisions etc). Adding calls to _ensure_real should only be done when
1422
bringing up new functionality, adding fallbacks for smart methods that
1423
require a fallback path, and never to replace an existing smart method
1424
invocation. If in doubt chat to the bzr network team.
1426
if self._real_repository is None:
1427
if 'hpssvfs' in debug.debug_flags:
1429
warning('VFS Repository access triggered\n%s',
1430
''.join(traceback.format_stack()))
1431
self._unstacked_provider.missing_keys.clear()
1432
self.controldir._ensure_real()
1433
self._set_real_repository(
1434
self.controldir._real_bzrdir.open_repository())
1436
def _translate_error(self, err, **context):
1437
self.controldir._translate_error(err, repository=self, **context)
1439
def find_text_key_references(self):
1440
"""Find the text key references within the repository.
1442
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1443
to whether they were referred to by the inventory of the
1444
revision_id that they contain. The inventory texts from all present
1445
revision ids are assessed to generate this report.
1448
return self._real_repository.find_text_key_references()
1450
def _generate_text_key_index(self):
1451
"""Generate a new text key index for the repository.
1453
This is an expensive function that will take considerable time to run.
1455
:return: A dict mapping (file_id, revision_id) tuples to a list of
1456
parents, also (file_id, revision_id) tuples.
1459
return self._real_repository._generate_text_key_index()
1461
def _get_revision_graph(self, revision_id):
1462
"""Private method for using with old (< 1.2) servers to fallback."""
1463
if revision_id is None:
1465
elif _mod_revision.is_null(revision_id):
1468
path = self.controldir._path_for_remote_call(self._client)
1469
response = self._call_expecting_body(
1470
b'Repository.get_revision_graph', path, revision_id)
1471
response_tuple, response_handler = response
1472
if response_tuple[0] != b'ok':
1473
raise errors.UnexpectedSmartServerResponse(response_tuple)
1474
coded = response_handler.read_body_bytes()
1476
# no revisions in this repository!
1478
lines = coded.split(b'\n')
1481
d = tuple(line.split())
1482
revision_graph[d[0]] = d[1:]
1484
return revision_graph
1486
def _get_sink(self):
1487
"""See Repository._get_sink()."""
1488
return RemoteStreamSink(self)
1490
def _get_source(self, to_format):
1491
"""Return a source for streaming from this repository."""
1492
return RemoteStreamSource(self, to_format)
1494
def get_file_graph(self):
1495
with self.lock_read():
1496
return graph.Graph(self.texts)
1498
def has_revision(self, revision_id):
1499
"""True if this repository has a copy of the revision."""
1500
# Copy of breezy.repository.Repository.has_revision
1501
with self.lock_read():
1502
return revision_id in self.has_revisions((revision_id,))
1504
def has_revisions(self, revision_ids):
1505
"""Probe to find out the presence of multiple revisions.
1507
:param revision_ids: An iterable of revision_ids.
1508
:return: A set of the revision_ids that were present.
1510
with self.lock_read():
1511
# Copy of breezy.repository.Repository.has_revisions
1512
parent_map = self.get_parent_map(revision_ids)
1513
result = set(parent_map)
1514
if _mod_revision.NULL_REVISION in revision_ids:
1515
result.add(_mod_revision.NULL_REVISION)
1518
def _has_same_fallbacks(self, other_repo):
1519
"""Returns true if the repositories have the same fallbacks."""
1520
# XXX: copied from Repository; it should be unified into a base class
1521
# <https://bugs.launchpad.net/bzr/+bug/401622>
1522
my_fb = self._fallback_repositories
1523
other_fb = other_repo._fallback_repositories
1524
if len(my_fb) != len(other_fb):
1526
for f, g in zip(my_fb, other_fb):
1527
if not f.has_same_location(g):
1531
def has_same_location(self, other):
1532
# TODO: Move to RepositoryBase and unify with the regular Repository
1533
# one; unfortunately the tests rely on slightly different behaviour at
1534
# present -- mbp 20090710
1535
return (self.__class__ is other.__class__
1536
and self.controldir.transport.base == other.controldir.transport.base)
1538
def get_graph(self, other_repository=None):
1539
"""Return the graph for this repository format"""
1540
parents_provider = self._make_parents_provider(other_repository)
1541
return graph.Graph(parents_provider)
1543
def get_known_graph_ancestry(self, revision_ids):
1544
"""Return the known graph for a set of revision ids and their ancestors.
1546
with self.lock_read():
1547
revision_graph = dict(((key, value) for key, value in
1548
self.get_graph().iter_ancestry(revision_ids) if value is not None))
1549
revision_graph = _mod_repository._strip_NULL_ghosts(revision_graph)
1550
return graph.KnownGraph(revision_graph)
1552
def gather_stats(self, revid=None, committers=None):
1553
"""See Repository.gather_stats()."""
1554
path = self.controldir._path_for_remote_call(self._client)
1555
# revid can be None to indicate no revisions, not just NULL_REVISION
1556
if revid is None or _mod_revision.is_null(revid):
1560
if committers is None or not committers:
1561
fmt_committers = b'no'
1563
fmt_committers = b'yes'
1564
response_tuple, response_handler = self._call_expecting_body(
1565
b'Repository.gather_stats', path, fmt_revid, fmt_committers)
1566
if response_tuple[0] != b'ok':
1567
raise errors.UnexpectedSmartServerResponse(response_tuple)
1569
body = response_handler.read_body_bytes()
1571
for line in body.split(b'\n'):
1574
key, val_text = line.split(b':')
1575
key = key.decode('ascii')
1576
if key in ('revisions', 'size', 'committers'):
1577
result[key] = int(val_text)
1578
elif key in ('firstrev', 'latestrev'):
1579
values = val_text.split(b' ')[1:]
1580
result[key] = (float(values[0]), int(values[1]))
1584
def find_branches(self, using=False):
1585
"""See Repository.find_branches()."""
1586
# should be an API call to the server.
1588
return self._real_repository.find_branches(using=using)
1590
def get_physical_lock_status(self):
1591
"""See Repository.get_physical_lock_status()."""
1592
path = self.controldir._path_for_remote_call(self._client)
1594
response = self._call(b'Repository.get_physical_lock_status', path)
1595
except errors.UnknownSmartMethod:
1597
return self._real_repository.get_physical_lock_status()
1598
if response[0] not in (b'yes', b'no'):
1599
raise errors.UnexpectedSmartServerResponse(response)
1600
return (response[0] == b'yes')
1602
def is_in_write_group(self):
1603
"""Return True if there is an open write group.
1605
write groups are only applicable locally for the smart server..
1607
if self._write_group_tokens is not None:
1609
if self._real_repository:
1610
return self._real_repository.is_in_write_group()
1612
def is_locked(self):
1613
return self._lock_count >= 1
1615
def is_shared(self):
1616
"""See Repository.is_shared()."""
1617
path = self.controldir._path_for_remote_call(self._client)
1618
response = self._call(b'Repository.is_shared', path)
1619
if response[0] not in (b'yes', b'no'):
1620
raise SmartProtocolError(
1621
'unexpected response code %s' % (response,))
1622
return response[0] == b'yes'
1624
def is_write_locked(self):
1625
return self._lock_mode == 'w'
1627
def _warn_if_deprecated(self, branch=None):
1628
# If we have a real repository, the check will be done there, if we
1629
# don't the check will be done remotely.
1632
def lock_read(self):
1633
"""Lock the repository for read operations.
1635
:return: A breezy.lock.LogicalLockResult.
1637
# wrong eventually - want a local lock cache context
1638
if not self._lock_mode:
1639
self._note_lock('r')
1640
self._lock_mode = 'r'
1641
self._lock_count = 1
1642
self._unstacked_provider.enable_cache(cache_misses=True)
1643
if self._real_repository is not None:
1644
self._real_repository.lock_read()
1645
for repo in self._fallback_repositories:
1648
self._lock_count += 1
1649
return lock.LogicalLockResult(self.unlock)
1651
def _remote_lock_write(self, token):
1652
path = self.controldir._path_for_remote_call(self._client)
1655
err_context = {'token': token}
1656
response = self._call(b'Repository.lock_write', path, token,
1658
if response[0] == b'ok':
1659
ok, token = response
1662
raise errors.UnexpectedSmartServerResponse(response)
1664
def lock_write(self, token=None, _skip_rpc=False):
1665
if not self._lock_mode:
1666
self._note_lock('w')
1668
if self._lock_token is not None:
1669
if token != self._lock_token:
1670
raise errors.TokenMismatch(token, self._lock_token)
1671
self._lock_token = token
1673
self._lock_token = self._remote_lock_write(token)
1674
# if self._lock_token is None, then this is something like packs or
1675
# svn where we don't get to lock the repo, or a weave style repository
1676
# where we cannot lock it over the wire and attempts to do so will
1678
if self._real_repository is not None:
1679
self._real_repository.lock_write(token=self._lock_token)
1680
if token is not None:
1681
self._leave_lock = True
1683
self._leave_lock = False
1684
self._lock_mode = 'w'
1685
self._lock_count = 1
1686
cache_misses = self._real_repository is None
1687
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1688
for repo in self._fallback_repositories:
1689
# Writes don't affect fallback repos
1691
elif self._lock_mode == 'r':
1692
raise errors.ReadOnlyError(self)
1694
self._lock_count += 1
1695
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1697
def leave_lock_in_place(self):
1698
if not self._lock_token:
1699
raise NotImplementedError(self.leave_lock_in_place)
1700
self._leave_lock = True
1702
def dont_leave_lock_in_place(self):
1703
if not self._lock_token:
1704
raise NotImplementedError(self.dont_leave_lock_in_place)
1705
self._leave_lock = False
1707
def _set_real_repository(self, repository):
1708
"""Set the _real_repository for this repository.
1710
:param repository: The repository to fallback to for non-hpss
1711
implemented operations.
1713
if self._real_repository is not None:
1714
# Replacing an already set real repository.
1715
# We cannot do this [currently] if the repository is locked -
1716
# synchronised state might be lost.
1717
if self.is_locked():
1718
raise AssertionError('_real_repository is already set')
1719
if isinstance(repository, RemoteRepository):
1720
raise AssertionError()
1721
self._real_repository = repository
1722
# three code paths happen here:
1723
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1724
# up stacking. In this case self._fallback_repositories is [], and the
1725
# real repo is already setup. Preserve the real repo and
1726
# RemoteRepository.add_fallback_repository will avoid adding
1728
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1729
# ensure_real is triggered from a branch, the real repository to
1730
# set already has a matching list with separate instances, but
1731
# as they are also RemoteRepositories we don't worry about making the
1732
# lists be identical.
1733
# 3) new servers, RemoteRepository.ensure_real is triggered before
1734
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1735
# and need to populate it.
1736
if (self._fallback_repositories
1737
and len(self._real_repository._fallback_repositories)
1738
!= len(self._fallback_repositories)):
1739
if len(self._real_repository._fallback_repositories):
1740
raise AssertionError(
1741
"cannot cleanly remove existing _fallback_repositories")
1742
for fb in self._fallback_repositories:
1743
self._real_repository.add_fallback_repository(fb)
1744
if self._lock_mode == 'w':
1745
# if we are already locked, the real repository must be able to
1746
# acquire the lock with our token.
1747
self._real_repository.lock_write(self._lock_token)
1748
elif self._lock_mode == 'r':
1749
self._real_repository.lock_read()
1750
if self._write_group_tokens is not None:
1751
# if we are already in a write group, resume it
1752
self._real_repository.resume_write_group(self._write_group_tokens)
1753
self._write_group_tokens = None
1755
def start_write_group(self):
1756
"""Start a write group on the decorated repository.
1758
Smart methods perform operations in a single step so this API
1759
is not really applicable except as a compatibility thunk
1760
for older plugins that don't use e.g. the CommitBuilder
1763
if self._real_repository:
1765
return self._real_repository.start_write_group()
1766
if not self.is_write_locked():
1767
raise errors.NotWriteLocked(self)
1768
if self._write_group_tokens is not None:
1769
raise errors.BzrError('already in a write group')
1770
path = self.controldir._path_for_remote_call(self._client)
1772
response = self._call(b'Repository.start_write_group', path,
1774
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1776
return self._real_repository.start_write_group()
1777
if response[0] != b'ok':
1778
raise errors.UnexpectedSmartServerResponse(response)
1779
self._write_group_tokens = [
1780
token.decode('utf-8') for token in response[1]]
1782
def _unlock(self, token):
1783
path = self.controldir._path_for_remote_call(self._client)
1785
# with no token the remote repository is not persistently locked.
1787
err_context = {'token': token}
1788
response = self._call(b'Repository.unlock', path, token,
1790
if response == (b'ok',):
1793
raise errors.UnexpectedSmartServerResponse(response)
1795
@only_raises(errors.LockNotHeld, errors.LockBroken)
1797
if not self._lock_count:
1798
return lock.cant_unlock_not_held(self)
1799
self._lock_count -= 1
1800
if self._lock_count > 0:
1802
self._unstacked_provider.disable_cache()
1803
old_mode = self._lock_mode
1804
self._lock_mode = None
1806
# The real repository is responsible at present for raising an
1807
# exception if it's in an unfinished write group. However, it
1808
# normally will *not* actually remove the lock from disk - that's
1809
# done by the server on receiving the Repository.unlock call.
1810
# This is just to let the _real_repository stay up to date.
1811
if self._real_repository is not None:
1812
self._real_repository.unlock()
1813
elif self._write_group_tokens is not None:
1814
self.abort_write_group()
1816
# The rpc-level lock should be released even if there was a
1817
# problem releasing the vfs-based lock.
1819
# Only write-locked repositories need to make a remote method
1820
# call to perform the unlock.
1821
old_token = self._lock_token
1822
self._lock_token = None
1823
if not self._leave_lock:
1824
self._unlock(old_token)
1825
# Fallbacks are always 'lock_read()' so we don't pay attention to
1827
for repo in self._fallback_repositories:
1830
def break_lock(self):
1831
# should hand off to the network
1832
path = self.controldir._path_for_remote_call(self._client)
1834
response = self._call(b"Repository.break_lock", path)
1835
except errors.UnknownSmartMethod:
1837
return self._real_repository.break_lock()
1838
if response != (b'ok',):
1839
raise errors.UnexpectedSmartServerResponse(response)
1841
def _get_tarball(self, compression):
1842
"""Return a TemporaryFile containing a repository tarball.
1844
Returns None if the server does not support sending tarballs.
1847
path = self.controldir._path_for_remote_call(self._client)
1849
response, protocol = self._call_expecting_body(
1850
b'Repository.tarball', path, compression.encode('ascii'))
1851
except errors.UnknownSmartMethod:
1852
protocol.cancel_read_body()
1854
if response[0] == b'ok':
1855
# Extract the tarball and return it
1856
t = tempfile.NamedTemporaryFile()
1857
# TODO: rpc layer should read directly into it...
1858
t.write(protocol.read_body_bytes())
1861
raise errors.UnexpectedSmartServerResponse(response)
1863
def sprout(self, to_bzrdir, revision_id=None):
1864
"""Create a descendent repository for new development.
1866
Unlike clone, this does not copy the settings of the repository.
1868
with self.lock_read():
1869
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1870
dest_repo.fetch(self, revision_id=revision_id)
1873
def _create_sprouting_repo(self, a_controldir, shared):
1874
if not isinstance(a_controldir._format, self.controldir._format.__class__):
1875
# use target default format.
1876
dest_repo = a_controldir.create_repository()
1878
# Most control formats need the repository to be specifically
1879
# created, but on some old all-in-one formats it's not needed
1881
dest_repo = self._format.initialize(
1882
a_controldir, shared=shared)
1883
except errors.UninitializableFormat:
1884
dest_repo = a_controldir.open_repository()
1887
# These methods are just thin shims to the VFS object for now.
1889
def revision_tree(self, revision_id):
1890
with self.lock_read():
1891
revision_id = _mod_revision.ensure_null(revision_id)
1892
if revision_id == _mod_revision.NULL_REVISION:
1893
return InventoryRevisionTree(self,
1894
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1896
return list(self.revision_trees([revision_id]))[0]
1898
def get_serializer_format(self):
1899
path = self.controldir._path_for_remote_call(self._client)
1901
response = self._call(b'VersionedFileRepository.get_serializer_format',
1903
except errors.UnknownSmartMethod:
1905
return self._real_repository.get_serializer_format()
1906
if response[0] != b'ok':
1907
raise errors.UnexpectedSmartServerResponse(response)
1910
def get_commit_builder(self, branch, parents, config, timestamp=None,
1911
timezone=None, committer=None, revprops=None,
1912
revision_id=None, lossy=False):
1913
"""Obtain a CommitBuilder for this repository.
1915
:param branch: Branch to commit to.
1916
:param parents: Revision ids of the parents of the new revision.
1917
:param config: Configuration to use.
1918
:param timestamp: Optional timestamp recorded for commit.
1919
:param timezone: Optional timezone for timestamp.
1920
:param committer: Optional committer to set for commit.
1921
:param revprops: Optional dictionary of revision properties.
1922
:param revision_id: Optional revision id.
1923
:param lossy: Whether to discard data that can not be natively
1924
represented, when pushing to a foreign VCS
1926
if self._fallback_repositories and not self._format.supports_chks:
1927
raise errors.BzrError("Cannot commit directly to a stacked branch"
1928
" in pre-2a formats. See "
1929
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1930
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1931
result = commit_builder_kls(self, parents, config,
1932
timestamp, timezone, committer, revprops, revision_id,
1934
self.start_write_group()
1937
def add_fallback_repository(self, repository):
1938
"""Add a repository to use for looking up data not held locally.
1940
:param repository: A repository.
1942
if not self._format.supports_external_lookups:
1943
raise errors.UnstackableRepositoryFormat(
1944
self._format.network_name(), self.base)
1945
# We need to accumulate additional repositories here, to pass them in
1948
# Make the check before we lock: this raises an exception.
1949
self._check_fallback_repository(repository)
1950
if self.is_locked():
1951
# We will call fallback.unlock() when we transition to the unlocked
1952
# state, so always add a lock here. If a caller passes us a locked
1953
# repository, they are responsible for unlocking it later.
1954
repository.lock_read()
1955
self._fallback_repositories.append(repository)
1956
# If self._real_repository was parameterised already (e.g. because a
1957
# _real_branch had its get_stacked_on_url method called), then the
1958
# repository to be added may already be in the _real_repositories list.
1959
if self._real_repository is not None:
1960
fallback_locations = [repo.user_url for repo in
1961
self._real_repository._fallback_repositories]
1962
if repository.user_url not in fallback_locations:
1963
self._real_repository.add_fallback_repository(repository)
1965
def _check_fallback_repository(self, repository):
1966
"""Check that this repository can fallback to repository safely.
1968
Raise an error if not.
1970
:param repository: A repository to fallback to.
1972
return _mod_repository.InterRepository._assert_same_model(
1975
def add_inventory(self, revid, inv, parents):
1977
return self._real_repository.add_inventory(revid, inv, parents)
1979
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1980
parents, basis_inv=None, propagate_caches=False):
1982
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1983
delta, new_revision_id, parents, basis_inv=basis_inv,
1984
propagate_caches=propagate_caches)
1986
def add_revision(self, revision_id, rev, inv=None):
1987
_mod_revision.check_not_reserved_id(revision_id)
1988
key = (revision_id,)
1989
# check inventory present
1990
if not self.inventories.get_parent_map([key]):
1992
raise errors.WeaveRevisionNotPresent(revision_id,
1995
# yes, this is not suitable for adding with ghosts.
1996
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1999
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
2000
self._add_revision(rev)
2002
def _add_revision(self, rev):
2003
if self._real_repository is not None:
2004
return self._real_repository._add_revision(rev)
2005
text = self._serializer.write_revision_to_string(rev)
2006
key = (rev.revision_id,)
2007
parents = tuple((parent,) for parent in rev.parent_ids)
2008
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
2009
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
2010
self._format, self._write_group_tokens)
2012
def get_inventory(self, revision_id):
2013
with self.lock_read():
2014
return list(self.iter_inventories([revision_id]))[0]
2016
def _iter_inventories_rpc(self, revision_ids, ordering):
2017
if ordering is None:
2018
ordering = 'unordered'
2019
path = self.controldir._path_for_remote_call(self._client)
2020
body = b"\n".join(revision_ids)
2021
response_tuple, response_handler = (
2022
self._call_with_body_bytes_expecting_body(
2023
b"VersionedFileRepository.get_inventories",
2024
(path, ordering.encode('ascii')), body))
2025
if response_tuple[0] != b"ok":
2026
raise errors.UnexpectedSmartServerResponse(response_tuple)
2027
deserializer = inventory_delta.InventoryDeltaDeserializer()
2028
byte_stream = response_handler.read_streamed_body()
2029
decoded = smart_repo._byte_stream_to_stream(byte_stream)
2031
# no results whatsoever
2033
src_format, stream = decoded
2034
if src_format.network_name() != self._format.network_name():
2035
raise AssertionError(
2036
"Mismatched RemoteRepository and stream src %r, %r" % (
2037
src_format.network_name(), self._format.network_name()))
2038
# ignore the src format, it's not really relevant
2039
prev_inv = Inventory(root_id=None,
2040
revision_id=_mod_revision.NULL_REVISION)
2041
# there should be just one substream, with inventory deltas
2043
substream_kind, substream = next(stream)
2044
except StopIteration:
2046
if substream_kind != "inventory-deltas":
2047
raise AssertionError(
2048
"Unexpected stream %r received" % substream_kind)
2049
for record in substream:
2050
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
2051
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
2052
if parent_id != prev_inv.revision_id:
2053
raise AssertionError("invalid base %r != %r" % (parent_id,
2054
prev_inv.revision_id))
2055
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
2056
yield inv, inv.revision_id
2059
def _iter_inventories_vfs(self, revision_ids, ordering=None):
2061
return self._real_repository._iter_inventories(revision_ids, ordering)
2063
def iter_inventories(self, revision_ids, ordering=None):
2064
"""Get many inventories by revision_ids.
2066
This will buffer some or all of the texts used in constructing the
2067
inventories in memory, but will only parse a single inventory at a
2070
:param revision_ids: The expected revision ids of the inventories.
2071
:param ordering: optional ordering, e.g. 'topological'. If not
2072
specified, the order of revision_ids will be preserved (by
2073
buffering if necessary).
2074
:return: An iterator of inventories.
2076
if ((None in revision_ids) or
2077
(_mod_revision.NULL_REVISION in revision_ids)):
2078
raise ValueError('cannot get null revision inventory')
2079
for inv, revid in self._iter_inventories(revision_ids, ordering):
2081
raise errors.NoSuchRevision(self, revid)
2084
def _iter_inventories(self, revision_ids, ordering=None):
2085
if len(revision_ids) == 0:
2087
missing = set(revision_ids)
2088
if ordering is None:
2089
order_as_requested = True
2091
order = list(revision_ids)
2093
next_revid = order.pop()
2095
order_as_requested = False
2096
if ordering != 'unordered' and self._fallback_repositories:
2097
raise ValueError('unsupported ordering %r' % ordering)
2098
iter_inv_fns = [self._iter_inventories_rpc] + [
2099
fallback._iter_inventories for fallback in
2100
self._fallback_repositories]
2102
for iter_inv in iter_inv_fns:
2103
request = [revid for revid in revision_ids if revid in missing]
2104
for inv, revid in iter_inv(request, ordering):
2107
missing.remove(inv.revision_id)
2108
if ordering != 'unordered':
2112
if order_as_requested:
2113
# Yield as many results as we can while preserving order.
2114
while next_revid in invs:
2115
inv = invs.pop(next_revid)
2116
yield inv, inv.revision_id
2118
next_revid = order.pop()
2120
# We still want to fully consume the stream, just
2121
# in case it is not actually finished at this point
2124
except errors.UnknownSmartMethod:
2125
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2129
if order_as_requested:
2130
if next_revid is not None:
2131
yield None, next_revid
2134
yield invs.get(revid), revid
2137
yield None, missing.pop()
2139
def get_revision(self, revision_id):
2140
with self.lock_read():
2141
return self.get_revisions([revision_id])[0]
2143
def get_transaction(self):
2145
return self._real_repository.get_transaction()
2147
def clone(self, a_controldir, revision_id=None):
2148
with self.lock_read():
2149
dest_repo = self._create_sprouting_repo(
2150
a_controldir, shared=self.is_shared())
2151
self.copy_content_into(dest_repo, revision_id)
2154
def make_working_trees(self):
2155
"""See Repository.make_working_trees"""
2156
path = self.controldir._path_for_remote_call(self._client)
2158
response = self._call(b'Repository.make_working_trees', path)
2159
except errors.UnknownSmartMethod:
2161
return self._real_repository.make_working_trees()
2162
if response[0] not in (b'yes', b'no'):
2163
raise SmartProtocolError(
2164
'unexpected response code %s' % (response,))
2165
return response[0] == b'yes'
2167
def refresh_data(self):
2168
"""Re-read any data needed to synchronise with disk.
2170
This method is intended to be called after another repository instance
2171
(such as one used by a smart server) has inserted data into the
2172
repository. On all repositories this will work outside of write groups.
2173
Some repository formats (pack and newer for breezy native formats)
2174
support refresh_data inside write groups. If called inside a write
2175
group on a repository that does not support refreshing in a write group
2176
IsInWriteGroupError will be raised.
2178
if self._real_repository is not None:
2179
self._real_repository.refresh_data()
2180
# Refresh the parents cache for this object
2181
self._unstacked_provider.disable_cache()
2182
self._unstacked_provider.enable_cache()
2184
def revision_ids_to_search_result(self, result_set):
2185
"""Convert a set of revision ids to a graph SearchResult."""
2186
result_parents = set()
2187
for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
2188
result_parents.update(parents)
2189
included_keys = result_set.intersection(result_parents)
2190
start_keys = result_set.difference(included_keys)
2191
exclude_keys = result_parents.difference(result_set)
2192
result = vf_search.SearchResult(start_keys, exclude_keys,
2193
len(result_set), result_set)
2196
def search_missing_revision_ids(self, other,
2197
find_ghosts=True, revision_ids=None, if_present_ids=None,
2199
"""Return the revision ids that other has that this does not.
2201
These are returned in topological order.
2203
revision_id: only return revision ids included by revision_id.
2205
with self.lock_read():
2206
inter_repo = _mod_repository.InterRepository.get(other, self)
2207
return inter_repo.search_missing_revision_ids(
2208
find_ghosts=find_ghosts, revision_ids=revision_ids,
2209
if_present_ids=if_present_ids, limit=limit)
2211
def fetch(self, source, revision_id=None, find_ghosts=False,
2213
# No base implementation to use as RemoteRepository is not a subclass
2214
# of Repository; so this is a copy of Repository.fetch().
2215
if fetch_spec is not None and revision_id is not None:
2216
raise AssertionError(
2217
"fetch_spec and revision_id are mutually exclusive.")
2218
if self.is_in_write_group():
2219
raise errors.InternalBzrError(
2220
"May not fetch while in a write group.")
2221
# fast path same-url fetch operations
2222
if (self.has_same_location(source) and
2223
fetch_spec is None and
2224
self._has_same_fallbacks(source)):
2225
# check that last_revision is in 'from' and then return a
2227
if (revision_id is not None
2228
and not _mod_revision.is_null(revision_id)):
2229
self.get_revision(revision_id)
2231
# if there is no specific appropriate InterRepository, this will get
2232
# the InterRepository base class, which raises an
2233
# IncompatibleRepositories when asked to fetch.
2234
inter = _mod_repository.InterRepository.get(source, self)
2235
if (fetch_spec is not None
2236
and not getattr(inter, "supports_fetch_spec", False)):
2237
raise errors.UnsupportedOperation(
2238
"fetch_spec not supported for %r" % inter)
2239
return inter.fetch(revision_id=revision_id,
2240
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2242
def create_bundle(self, target, base, fileobj, format=None):
2244
self._real_repository.create_bundle(target, base, fileobj, format)
2246
def fileids_altered_by_revision_ids(self, revision_ids):
2248
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2250
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2252
return self._real_repository._get_versioned_file_checker(
2253
revisions, revision_versions_cache)
2255
def _iter_files_bytes_rpc(self, desired_files, absent):
2256
path = self.controldir._path_for_remote_call(self._client)
2259
for (file_id, revid, identifier) in desired_files:
2260
lines.append(b''.join([
2261
osutils.safe_file_id(file_id),
2263
osutils.safe_revision_id(revid)]))
2264
identifiers.append(identifier)
2265
(response_tuple, response_handler) = (
2266
self._call_with_body_bytes_expecting_body(
2267
b"Repository.iter_files_bytes", (path, ), b"\n".join(lines)))
2268
if response_tuple != (b'ok', ):
2269
response_handler.cancel_read_body()
2270
raise errors.UnexpectedSmartServerResponse(response_tuple)
2271
byte_stream = response_handler.read_streamed_body()
2273
def decompress_stream(start, byte_stream, unused):
2274
decompressor = zlib.decompressobj()
2275
yield decompressor.decompress(start)
2276
while decompressor.unused_data == b"":
2278
data = next(byte_stream)
2279
except StopIteration:
2281
yield decompressor.decompress(data)
2282
yield decompressor.flush()
2283
unused.append(decompressor.unused_data)
2286
while b"\n" not in unused:
2288
unused += next(byte_stream)
2289
except StopIteration:
2291
header, rest = unused.split(b"\n", 1)
2292
args = header.split(b"\0")
2293
if args[0] == b"absent":
2294
absent[identifiers[int(args[3])]] = (args[1], args[2])
2297
elif args[0] == b"ok":
2300
raise errors.UnexpectedSmartServerResponse(args)
2302
yield (identifiers[idx],
2303
decompress_stream(rest, byte_stream, unused_chunks))
2304
unused = b"".join(unused_chunks)
2306
def iter_files_bytes(self, desired_files):
2307
"""See Repository.iter_file_bytes.
2311
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2312
desired_files, absent):
2313
yield identifier, bytes_iterator
2314
for fallback in self._fallback_repositories:
2317
desired_files = [(key[0], key[1], identifier)
2318
for identifier, key in viewitems(absent)]
2319
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2320
del absent[identifier]
2321
yield identifier, bytes_iterator
2323
# There may be more missing items, but raise an exception
2325
missing_identifier = next(iter(absent))
2326
missing_key = absent[missing_identifier]
2327
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2328
file_id=missing_key[0])
2329
except errors.UnknownSmartMethod:
2331
for (identifier, bytes_iterator) in (
2332
self._real_repository.iter_files_bytes(desired_files)):
2333
yield identifier, bytes_iterator
2335
def get_cached_parent_map(self, revision_ids):
2336
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2337
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2339
def get_parent_map(self, revision_ids):
2340
"""See breezy.Graph.get_parent_map()."""
2341
return self._make_parents_provider().get_parent_map(revision_ids)
2343
def _get_parent_map_rpc(self, keys):
2344
"""Helper for get_parent_map that performs the RPC."""
2345
medium = self._client._medium
2346
if medium._is_remote_before((1, 2)):
2347
# We already found out that the server can't understand
2348
# Repository.get_parent_map requests, so just fetch the whole
2351
# Note that this reads the whole graph, when only some keys are
2352
# wanted. On this old server there's no way (?) to get them all
2353
# in one go, and the user probably will have seen a warning about
2354
# the server being old anyhow.
2355
rg = self._get_revision_graph(None)
2356
# There is an API discrepancy between get_parent_map and
2357
# get_revision_graph. Specifically, a "key:()" pair in
2358
# get_revision_graph just means a node has no parents. For
2359
# "get_parent_map" it means the node is a ghost. So fix up the
2360
# graph to correct this.
2361
# https://bugs.launchpad.net/bzr/+bug/214894
2362
# There is one other "bug" which is that ghosts in
2363
# get_revision_graph() are not returned at all. But we won't worry
2364
# about that for now.
2365
for node_id, parent_ids in viewitems(rg):
2366
if parent_ids == ():
2367
rg[node_id] = (NULL_REVISION,)
2368
rg[NULL_REVISION] = ()
2373
raise ValueError('get_parent_map(None) is not valid')
2374
if NULL_REVISION in keys:
2375
keys.discard(NULL_REVISION)
2376
found_parents = {NULL_REVISION: ()}
2378
return found_parents
2381
# TODO(Needs analysis): We could assume that the keys being requested
2382
# from get_parent_map are in a breadth first search, so typically they
2383
# will all be depth N from some common parent, and we don't have to
2384
# have the server iterate from the root parent, but rather from the
2385
# keys we're searching; and just tell the server the keyspace we
2386
# already have; but this may be more traffic again.
2388
# Transform self._parents_map into a search request recipe.
2389
# TODO: Manage this incrementally to avoid covering the same path
2390
# repeatedly. (The server will have to on each request, but the less
2391
# work done the better).
2393
# Negative caching notes:
2394
# new server sends missing when a request including the revid
2395
# 'include-missing:' is present in the request.
2396
# missing keys are serialised as missing:X, and we then call
2397
# provider.note_missing(X) for-all X
2398
parents_map = self._unstacked_provider.get_cached_map()
2399
if parents_map is None:
2400
# Repository is not locked, so there's no cache.
2402
if _DEFAULT_SEARCH_DEPTH <= 0:
2403
(start_set, stop_keys,
2404
key_count) = vf_search.search_result_from_parent_map(
2405
parents_map, self._unstacked_provider.missing_keys)
2407
(start_set, stop_keys,
2408
key_count) = vf_search.limited_search_result_from_parent_map(
2409
parents_map, self._unstacked_provider.missing_keys,
2410
keys, depth=_DEFAULT_SEARCH_DEPTH)
2411
recipe = ('manual', start_set, stop_keys, key_count)
2412
body = self._serialise_search_recipe(recipe)
2413
path = self.controldir._path_for_remote_call(self._client)
2415
if not isinstance(key, bytes):
2417
"key %r not a bytes string" % (key,))
2418
verb = b'Repository.get_parent_map'
2419
args = (path, b'include-missing:') + tuple(keys)
2421
response = self._call_with_body_bytes_expecting_body(
2423
except errors.UnknownSmartMethod:
2424
# Server does not support this method, so get the whole graph.
2425
# Worse, we have to force a disconnection, because the server now
2426
# doesn't realise it has a body on the wire to consume, so the
2427
# only way to recover is to abandon the connection.
2429
'Server is too old for fast get_parent_map, reconnecting. '
2430
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2432
# To avoid having to disconnect repeatedly, we keep track of the
2433
# fact the server doesn't understand remote methods added in 1.2.
2434
medium._remember_remote_is_before((1, 2))
2435
# Recurse just once and we should use the fallback code.
2436
return self._get_parent_map_rpc(keys)
2437
response_tuple, response_handler = response
2438
if response_tuple[0] not in [b'ok']:
2439
response_handler.cancel_read_body()
2440
raise errors.UnexpectedSmartServerResponse(response_tuple)
2441
if response_tuple[0] == b'ok':
2442
coded = bz2.decompress(response_handler.read_body_bytes())
2444
# no revisions found
2446
lines = coded.split(b'\n')
2449
d = tuple(line.split())
2451
revision_graph[d[0]] = d[1:]
2454
if d[0].startswith(b'missing:'):
2456
self._unstacked_provider.note_missing_key(revid)
2458
# no parents - so give the Graph result
2460
revision_graph[d[0]] = (NULL_REVISION,)
2461
return revision_graph
2463
def get_signature_text(self, revision_id):
2464
with self.lock_read():
2465
path = self.controldir._path_for_remote_call(self._client)
2467
response_tuple, response_handler = self._call_expecting_body(
2468
b'Repository.get_revision_signature_text', path, revision_id)
2469
except errors.UnknownSmartMethod:
2471
return self._real_repository.get_signature_text(revision_id)
2472
except errors.NoSuchRevision as err:
2473
for fallback in self._fallback_repositories:
2475
return fallback.get_signature_text(revision_id)
2476
except errors.NoSuchRevision:
2480
if response_tuple[0] != b'ok':
2481
raise errors.UnexpectedSmartServerResponse(response_tuple)
2482
return response_handler.read_body_bytes()
2484
def _get_inventory_xml(self, revision_id):
2485
with self.lock_read():
2486
# This call is used by older working tree formats,
2487
# which stored a serialized basis inventory.
2489
return self._real_repository._get_inventory_xml(revision_id)
2491
def reconcile(self, other=None, thorough=False):
2492
from ..reconcile import ReconcileResult
2493
with self.lock_write():
2494
path = self.controldir._path_for_remote_call(self._client)
2496
response, handler = self._call_expecting_body(
2497
b'Repository.reconcile', path, self._lock_token)
2498
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2500
return self._real_repository.reconcile(other=other, thorough=thorough)
2501
if response != (b'ok', ):
2502
raise errors.UnexpectedSmartServerResponse(response)
2503
body = handler.read_body_bytes()
2504
result = ReconcileResult()
2505
result.garbage_inventories = None
2506
result.inconsistent_parents = None
2507
result.aborted = None
2508
for line in body.split(b'\n'):
2511
key, val_text = line.split(b':')
2512
if key == b"garbage_inventories":
2513
result.garbage_inventories = int(val_text)
2514
elif key == b"inconsistent_parents":
2515
result.inconsistent_parents = int(val_text)
2517
mutter("unknown reconcile key %r" % key)
2520
def all_revision_ids(self):
2521
path = self.controldir._path_for_remote_call(self._client)
2523
response_tuple, response_handler = self._call_expecting_body(
2524
b"Repository.all_revision_ids", path)
2525
except errors.UnknownSmartMethod:
2527
return self._real_repository.all_revision_ids()
2528
if response_tuple != (b"ok", ):
2529
raise errors.UnexpectedSmartServerResponse(response_tuple)
2530
revids = set(response_handler.read_body_bytes().splitlines())
2531
for fallback in self._fallback_repositories:
2532
revids.update(set(fallback.all_revision_ids()))
2535
def _filtered_revision_trees(self, revision_ids, file_ids):
2536
"""Return Tree for a revision on this branch with only some files.
2538
:param revision_ids: a sequence of revision-ids;
2539
a revision-id may not be None or b'null:'
2540
:param file_ids: if not None, the result is filtered
2541
so that only those file-ids, their parents and their
2542
children are included.
2544
inventories = self.iter_inventories(revision_ids)
2545
for inv in inventories:
2546
# Should we introduce a FilteredRevisionTree class rather
2547
# than pre-filter the inventory here?
2548
filtered_inv = inv.filter(file_ids)
2549
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2551
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2552
with self.lock_read():
2553
medium = self._client._medium
2554
if medium._is_remote_before((1, 2)):
2556
for delta in self._real_repository.get_deltas_for_revisions(
2557
revisions, specific_fileids):
2560
# Get the revision-ids of interest
2561
required_trees = set()
2562
for revision in revisions:
2563
required_trees.add(revision.revision_id)
2564
required_trees.update(revision.parent_ids[:1])
2566
# Get the matching filtered trees. Note that it's more
2567
# efficient to pass filtered trees to changes_from() rather
2568
# than doing the filtering afterwards. changes_from() could
2569
# arguably do the filtering itself but it's path-based, not
2570
# file-id based, so filtering before or afterwards is
2572
if specific_fileids is None:
2573
trees = dict((t.get_revision_id(), t) for
2574
t in self.revision_trees(required_trees))
2576
trees = dict((t.get_revision_id(), t) for
2577
t in self._filtered_revision_trees(required_trees,
2580
# Calculate the deltas
2581
for revision in revisions:
2582
if not revision.parent_ids:
2583
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2585
old_tree = trees[revision.parent_ids[0]]
2586
yield trees[revision.revision_id].changes_from(old_tree)
2588
def get_revision_delta(self, revision_id, specific_fileids=None):
2589
with self.lock_read():
2590
r = self.get_revision(revision_id)
2591
return list(self.get_deltas_for_revisions([r],
2592
specific_fileids=specific_fileids))[0]
2594
def revision_trees(self, revision_ids):
2595
with self.lock_read():
2596
inventories = self.iter_inventories(revision_ids)
2597
for inv in inventories:
2598
yield RemoteInventoryTree(self, inv, inv.revision_id)
2600
def get_revision_reconcile(self, revision_id):
2601
with self.lock_read():
2603
return self._real_repository.get_revision_reconcile(revision_id)
2605
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2606
with self.lock_read():
2608
return self._real_repository.check(revision_ids=revision_ids,
2609
callback_refs=callback_refs, check_repo=check_repo)
2611
def copy_content_into(self, destination, revision_id=None):
2612
"""Make a complete copy of the content in self into destination.
2614
This is a destructive operation! Do not use it on existing
2617
interrepo = _mod_repository.InterRepository.get(self, destination)
2618
return interrepo.copy_content(revision_id)
2620
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2621
# get a tarball of the remote repository, and copy from that into the
2624
# TODO: Maybe a progress bar while streaming the tarball?
2625
note(gettext("Copying repository content as tarball..."))
2626
tar_file = self._get_tarball('bz2')
2627
if tar_file is None:
2629
destination = to_bzrdir.create_repository()
2631
tar = tarfile.open('repository', fileobj=tar_file,
2633
tmpdir = osutils.mkdtemp()
2635
tar.extractall(tmpdir)
2636
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2637
tmp_repo = tmp_bzrdir.open_repository()
2638
tmp_repo.copy_content_into(destination, revision_id)
2640
osutils.rmtree(tmpdir)
2644
# TODO: Suggestion from john: using external tar is much faster than
2645
# python's tarfile library, but it may not work on windows.
2648
def inventories(self):
2649
"""Decorate the real repository for now.
2651
In the long term a full blown network facility is needed to
2652
avoid creating a real repository object locally.
2655
return self._real_repository.inventories
2657
def pack(self, hint=None, clean_obsolete_packs=False):
2658
"""Compress the data within the repository.
2663
body = b"".join([l.encode('ascii') + b"\n" for l in hint])
2664
with self.lock_write():
2665
path = self.controldir._path_for_remote_call(self._client)
2667
response, handler = self._call_with_body_bytes_expecting_body(
2668
b'Repository.pack', (path, self._lock_token,
2669
str(clean_obsolete_packs).encode('ascii')), body)
2670
except errors.UnknownSmartMethod:
2672
return self._real_repository.pack(hint=hint,
2673
clean_obsolete_packs=clean_obsolete_packs)
2674
handler.cancel_read_body()
2675
if response != (b'ok', ):
2676
raise errors.UnexpectedSmartServerResponse(response)
2679
def revisions(self):
2680
"""Decorate the real repository for now.
2682
In the long term a full blown network facility is needed.
2685
return self._real_repository.revisions
2687
def set_make_working_trees(self, new_value):
2689
new_value_str = b"True"
2691
new_value_str = b"False"
2692
path = self.controldir._path_for_remote_call(self._client)
2694
response = self._call(
2695
b'Repository.set_make_working_trees', path, new_value_str)
2696
except errors.UnknownSmartMethod:
2698
self._real_repository.set_make_working_trees(new_value)
2700
if response[0] != b'ok':
2701
raise errors.UnexpectedSmartServerResponse(response)
2704
def signatures(self):
2705
"""Decorate the real repository for now.
2707
In the long term a full blown network facility is needed to avoid
2708
creating a real repository object locally.
2711
return self._real_repository.signatures
2713
def sign_revision(self, revision_id, gpg_strategy):
2714
with self.lock_write():
2715
testament = _mod_testament.Testament.from_revision(
2717
plaintext = testament.as_short_text()
2718
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2722
"""Decorate the real repository for now.
2724
In the long term a full blown network facility is needed to avoid
2725
creating a real repository object locally.
2728
return self._real_repository.texts
2730
def _iter_revisions_rpc(self, revision_ids):
2731
body = b"\n".join(revision_ids)
2732
path = self.controldir._path_for_remote_call(self._client)
2733
response_tuple, response_handler = (
2734
self._call_with_body_bytes_expecting_body(
2735
b"Repository.iter_revisions", (path, ), body))
2736
if response_tuple[0] != b"ok":
2737
raise errors.UnexpectedSmartServerResponse(response_tuple)
2738
serializer_format = response_tuple[1].decode('ascii')
2739
serializer = serializer_format_registry.get(serializer_format)
2740
byte_stream = response_handler.read_streamed_body()
2741
decompressor = zlib.decompressobj()
2743
for bytes in byte_stream:
2744
chunks.append(decompressor.decompress(bytes))
2745
if decompressor.unused_data != b"":
2746
chunks.append(decompressor.flush())
2747
yield serializer.read_revision_from_string(b"".join(chunks))
2748
unused = decompressor.unused_data
2749
decompressor = zlib.decompressobj()
2750
chunks = [decompressor.decompress(unused)]
2751
chunks.append(decompressor.flush())
2752
text = b"".join(chunks)
2754
yield serializer.read_revision_from_string(b"".join(chunks))
2756
def iter_revisions(self, revision_ids):
2757
for rev_id in revision_ids:
2758
if not rev_id or not isinstance(rev_id, bytes):
2759
raise errors.InvalidRevisionId(
2760
revision_id=rev_id, branch=self)
2761
with self.lock_read():
2763
missing = set(revision_ids)
2764
for rev in self._iter_revisions_rpc(revision_ids):
2765
missing.remove(rev.revision_id)
2766
yield (rev.revision_id, rev)
2767
for fallback in self._fallback_repositories:
2770
for (revid, rev) in fallback.iter_revisions(missing):
2773
missing.remove(revid)
2774
for revid in missing:
2776
except errors.UnknownSmartMethod:
2778
for entry in self._real_repository.iter_revisions(revision_ids):
2781
def supports_rich_root(self):
2782
return self._format.rich_root_data
2785
def _serializer(self):
2786
return self._format._serializer
2788
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2789
with self.lock_write():
2790
signature = gpg_strategy.sign(plaintext, gpg.MODE_CLEAR)
2791
self.add_signature_text(revision_id, signature)
2793
def add_signature_text(self, revision_id, signature):
2794
if self._real_repository:
2795
# If there is a real repository the write group will
2796
# be in the real repository as well, so use that:
2798
return self._real_repository.add_signature_text(
2799
revision_id, signature)
2800
path = self.controldir._path_for_remote_call(self._client)
2801
response, handler = self._call_with_body_bytes_expecting_body(
2802
b'Repository.add_signature_text', (path, self._lock_token,
2804
tuple([token.encode('utf-8')
2805
for token in self._write_group_tokens]),
2807
handler.cancel_read_body()
2809
if response[0] != b'ok':
2810
raise errors.UnexpectedSmartServerResponse(response)
2811
self._write_group_tokens = [token.decode(
2812
'utf-8') for token in response[1:]]
2814
def has_signature_for_revision_id(self, revision_id):
2815
path = self.controldir._path_for_remote_call(self._client)
2817
response = self._call(b'Repository.has_signature_for_revision_id',
2819
except errors.UnknownSmartMethod:
2821
return self._real_repository.has_signature_for_revision_id(
2823
if response[0] not in (b'yes', b'no'):
2824
raise SmartProtocolError(
2825
'unexpected response code %s' % (response,))
2826
if response[0] == b'yes':
2828
for fallback in self._fallback_repositories:
2829
if fallback.has_signature_for_revision_id(revision_id):
2833
def verify_revision_signature(self, revision_id, gpg_strategy):
2834
with self.lock_read():
2835
if not self.has_signature_for_revision_id(revision_id):
2836
return gpg.SIGNATURE_NOT_SIGNED, None
2837
signature = self.get_signature_text(revision_id)
2839
testament = _mod_testament.Testament.from_revision(
2842
(status, key, signed_plaintext) = gpg_strategy.verify(signature)
2843
if testament.as_short_text() != signed_plaintext:
2844
return gpg.SIGNATURE_NOT_VALID, None
2845
return (status, key)
2847
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2849
return self._real_repository.item_keys_introduced_by(revision_ids,
2850
_files_pb=_files_pb)
2852
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2854
return self._real_repository._find_inconsistent_revision_parents(
2857
def _check_for_inconsistent_revision_parents(self):
2859
return self._real_repository._check_for_inconsistent_revision_parents()
2861
def _make_parents_provider(self, other=None):
2862
providers = [self._unstacked_provider]
2863
if other is not None:
2864
providers.insert(0, other)
2865
return graph.StackedParentsProvider(_LazyListJoin(
2866
providers, self._fallback_repositories))
2868
def _serialise_search_recipe(self, recipe):
2869
"""Serialise a graph search recipe.
2871
:param recipe: A search recipe (start, stop, count).
2872
:return: Serialised bytes.
2874
start_keys = b' '.join(recipe[1])
2875
stop_keys = b' '.join(recipe[2])
2876
count = str(recipe[3]).encode('ascii')
2877
return b'\n'.join((start_keys, stop_keys, count))
2879
def _serialise_search_result(self, search_result):
2880
parts = search_result.get_network_struct()
2881
return b'\n'.join(parts)
2884
path = self.controldir._path_for_remote_call(self._client)
2886
response = self._call(b'PackRepository.autopack', path)
2887
except errors.UnknownSmartMethod:
2889
self._real_repository._pack_collection.autopack()
2892
if response[0] != b'ok':
2893
raise errors.UnexpectedSmartServerResponse(response)
2895
def _revision_archive(self, revision_id, format, name, root, subdir,
2897
path = self.controldir._path_for_remote_call(self._client)
2898
format = format or ''
2900
subdir = subdir or ''
2901
force_mtime = int(force_mtime) if force_mtime is not None else None
2903
response, protocol = self._call_expecting_body(
2904
b'Repository.revision_archive', path,
2906
format.encode('ascii'),
2907
os.path.basename(name).encode('utf-8'),
2908
root.encode('utf-8'),
2909
subdir.encode('utf-8'),
2911
except errors.UnknownSmartMethod:
2913
if response[0] == b'ok':
2914
return iter([protocol.read_body_bytes()])
2915
raise errors.UnexpectedSmartServerResponse(response)
2917
def _annotate_file_revision(self, revid, tree_path, file_id, default_revision):
2918
path = self.controldir._path_for_remote_call(self._client)
2919
tree_path = tree_path.encode('utf-8')
2920
file_id = file_id or b''
2921
default_revision = default_revision or b''
2923
response, handler = self._call_expecting_body(
2924
b'Repository.annotate_file_revision', path,
2925
revid, tree_path, file_id, default_revision)
2926
except errors.UnknownSmartMethod:
2928
if response[0] != b'ok':
2929
raise errors.UnexpectedSmartServerResponse(response)
2930
return map(tuple, bencode.bdecode(handler.read_body_bytes()))
2933
class RemoteStreamSink(vf_repository.StreamSink):
2935
def _insert_real(self, stream, src_format, resume_tokens):
2936
self.target_repo._ensure_real()
2937
sink = self.target_repo._real_repository._get_sink()
2938
result = sink.insert_stream(stream, src_format, resume_tokens)
2940
self.target_repo.autopack()
2943
def insert_missing_keys(self, source, missing_keys):
2944
if (isinstance(source, RemoteStreamSource)
2945
and source.from_repository._client._medium == self.target_repo._client._medium):
2946
# Streaming from and to the same medium is tricky, since we don't support
2947
# more than one concurrent request. For now, just force VFS.
2948
stream = source._get_real_stream_for_missing_keys(missing_keys)
2950
stream = source.get_stream_for_missing_keys(missing_keys)
2951
return self.insert_stream_without_locking(stream,
2952
self.target_repo._format)
2954
def insert_stream(self, stream, src_format, resume_tokens):
2955
target = self.target_repo
2956
target._unstacked_provider.missing_keys.clear()
2957
candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
2958
if target._lock_token:
2959
candidate_calls.append(
2960
(b'Repository.insert_stream_locked', (1, 14)))
2961
lock_args = (target._lock_token or b'',)
2963
candidate_calls.append((b'Repository.insert_stream', (1, 13)))
2965
client = target._client
2966
medium = client._medium
2967
path = target.controldir._path_for_remote_call(client)
2968
# Probe for the verb to use with an empty stream before sending the
2969
# real stream to it. We do this both to avoid the risk of sending a
2970
# large request that is then rejected, and because we don't want to
2971
# implement a way to buffer, rewind, or restart the stream.
2973
for verb, required_version in candidate_calls:
2974
if medium._is_remote_before(required_version):
2977
# We've already done the probing (and set _is_remote_before) on
2978
# a previous insert.
2981
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2983
response = client.call_with_body_stream(
2984
(verb, path, b'') + lock_args, byte_stream)
2985
except errors.UnknownSmartMethod:
2986
medium._remember_remote_is_before(required_version)
2992
return self._insert_real(stream, src_format, resume_tokens)
2993
self._last_inv_record = None
2994
self._last_substream = None
2995
if required_version < (1, 19):
2996
# Remote side doesn't support inventory deltas. Wrap the stream to
2997
# make sure we don't send any. If the stream contains inventory
2998
# deltas we'll interrupt the smart insert_stream request and
3000
stream = self._stop_stream_if_inventory_delta(stream)
3001
byte_stream = smart_repo._stream_to_byte_stream(
3003
resume_tokens = b' '.join([token.encode('utf-8')
3004
for token in resume_tokens])
3005
response = client.call_with_body_stream(
3006
(verb, path, resume_tokens) + lock_args, byte_stream)
3007
if response[0][0] not in (b'ok', b'missing-basis'):
3008
raise errors.UnexpectedSmartServerResponse(response)
3009
if self._last_substream is not None:
3010
# The stream included an inventory-delta record, but the remote
3011
# side isn't new enough to support them. So we need to send the
3012
# rest of the stream via VFS.
3013
self.target_repo.refresh_data()
3014
return self._resume_stream_with_vfs(response, src_format)
3015
if response[0][0] == b'missing-basis':
3016
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3017
resume_tokens = [token.decode('utf-8') for token in tokens]
3018
return resume_tokens, set((entry[0].decode('utf-8'), ) + entry[1:] for entry in missing_keys)
3020
self.target_repo.refresh_data()
3023
def _resume_stream_with_vfs(self, response, src_format):
3024
"""Resume sending a stream via VFS, first resending the record and
3025
substream that couldn't be sent via an insert_stream verb.
3027
if response[0][0] == b'missing-basis':
3028
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3029
tokens = [token.decode('utf-8') for token in tokens]
3030
# Ignore missing_keys, we haven't finished inserting yet
3034
def resume_substream():
3035
# Yield the substream that was interrupted.
3036
for record in self._last_substream:
3038
self._last_substream = None
3040
def resume_stream():
3041
# Finish sending the interrupted substream
3042
yield ('inventory-deltas', resume_substream())
3043
# Then simply continue sending the rest of the stream.
3044
for substream_kind, substream in self._last_stream:
3045
yield substream_kind, substream
3046
return self._insert_real(resume_stream(), src_format, tokens)
3048
def _stop_stream_if_inventory_delta(self, stream):
3049
"""Normally this just lets the original stream pass-through unchanged.
3051
However if any 'inventory-deltas' substream occurs it will stop
3052
streaming, and store the interrupted substream and stream in
3053
self._last_substream and self._last_stream so that the stream can be
3054
resumed by _resume_stream_with_vfs.
3057
stream_iter = iter(stream)
3058
for substream_kind, substream in stream_iter:
3059
if substream_kind == 'inventory-deltas':
3060
self._last_substream = substream
3061
self._last_stream = stream_iter
3064
yield substream_kind, substream
3067
class RemoteStreamSource(vf_repository.StreamSource):
3068
"""Stream data from a remote server."""
3070
def get_stream(self, search):
3071
if (self.from_repository._fallback_repositories
3072
and self.to_format._fetch_order == 'topological'):
3073
return self._real_stream(self.from_repository, search)
3076
repos = [self.from_repository]
3082
repos.extend(repo._fallback_repositories)
3083
sources.append(repo)
3084
return self.missing_parents_chain(search, sources)
3086
def _get_real_stream_for_missing_keys(self, missing_keys):
3087
self.from_repository._ensure_real()
3088
real_repo = self.from_repository._real_repository
3089
real_source = real_repo._get_source(self.to_format)
3090
return real_source.get_stream_for_missing_keys(missing_keys)
3092
def get_stream_for_missing_keys(self, missing_keys):
3093
if not isinstance(self.from_repository, RemoteRepository):
3094
return self._get_real_stream_for_missing_keys(missing_keys)
3095
client = self.from_repository._client
3096
medium = client._medium
3097
if medium._is_remote_before((3, 0)):
3098
return self._get_real_stream_for_missing_keys(missing_keys)
3099
path = self.from_repository.controldir._path_for_remote_call(client)
3100
args = (path, self.to_format.network_name())
3101
search_bytes = b'\n'.join(
3102
[b'%s\t%s' % (key[0].encode('utf-8'), key[1]) for key in missing_keys])
3104
response, handler = self.from_repository._call_with_body_bytes_expecting_body(
3105
b'Repository.get_stream_for_missing_keys', args, search_bytes)
3106
except (errors.UnknownSmartMethod, errors.UnknownFormatError):
3107
return self._get_real_stream_for_missing_keys(missing_keys)
3108
if response[0] != b'ok':
3109
raise errors.UnexpectedSmartServerResponse(response)
3110
byte_stream = handler.read_streamed_body()
3111
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3112
self._record_counter)
3113
if src_format.network_name() != self.from_repository._format.network_name():
3114
raise AssertionError(
3115
"Mismatched RemoteRepository and stream src %r, %r" % (
3116
src_format.network_name(), repo._format.network_name()))
3119
def _real_stream(self, repo, search):
3120
"""Get a stream for search from repo.
3122
This never called RemoteStreamSource.get_stream, and is a helper
3123
for RemoteStreamSource._get_stream to allow getting a stream
3124
reliably whether fallback back because of old servers or trying
3125
to stream from a non-RemoteRepository (which the stacked support
3128
source = repo._get_source(self.to_format)
3129
if isinstance(source, RemoteStreamSource):
3131
source = repo._real_repository._get_source(self.to_format)
3132
return source.get_stream(search)
3134
def _get_stream(self, repo, search):
3135
"""Core worker to get a stream from repo for search.
3137
This is used by both get_stream and the stacking support logic. It
3138
deliberately gets a stream for repo which does not need to be
3139
self.from_repository. In the event that repo is not Remote, or
3140
cannot do a smart stream, a fallback is made to the generic
3141
repository._get_stream() interface, via self._real_stream.
3143
In the event of stacking, streams from _get_stream will not
3144
contain all the data for search - this is normal (see get_stream).
3146
:param repo: A repository.
3147
:param search: A search.
3149
# Fallbacks may be non-smart
3150
if not isinstance(repo, RemoteRepository):
3151
return self._real_stream(repo, search)
3152
client = repo._client
3153
medium = client._medium
3154
path = repo.controldir._path_for_remote_call(client)
3155
search_bytes = repo._serialise_search_result(search)
3156
args = (path, self.to_format.network_name())
3158
(b'Repository.get_stream_1.19', (1, 19)),
3159
(b'Repository.get_stream', (1, 13))]
3162
for verb, version in candidate_verbs:
3163
if medium._is_remote_before(version):
3166
response = repo._call_with_body_bytes_expecting_body(
3167
verb, args, search_bytes)
3168
except errors.UnknownSmartMethod:
3169
medium._remember_remote_is_before(version)
3170
except errors.UnknownErrorFromSmartServer as e:
3171
if isinstance(search, vf_search.EverythingResult):
3172
error_verb = e.error_from_smart_server.error_verb
3173
if error_verb == b'BadSearch':
3174
# Pre-2.4 servers don't support this sort of search.
3175
# XXX: perhaps falling back to VFS on BadSearch is a
3176
# good idea in general? It might provide a little bit
3177
# of protection against client-side bugs.
3178
medium._remember_remote_is_before((2, 4))
3182
response_tuple, response_handler = response
3186
return self._real_stream(repo, search)
3187
if response_tuple[0] != b'ok':
3188
raise errors.UnexpectedSmartServerResponse(response_tuple)
3189
byte_stream = response_handler.read_streamed_body()
3190
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3191
self._record_counter)
3192
if src_format.network_name() != repo._format.network_name():
3193
raise AssertionError(
3194
"Mismatched RemoteRepository and stream src %r, %r" % (
3195
src_format.network_name(), repo._format.network_name()))
3198
def missing_parents_chain(self, search, sources):
3199
"""Chain multiple streams together to handle stacking.
3201
:param search: The overall search to satisfy with streams.
3202
:param sources: A list of Repository objects to query.
3204
self.from_serialiser = self.from_repository._format._serializer
3205
self.seen_revs = set()
3206
self.referenced_revs = set()
3207
# If there are heads in the search, or the key count is > 0, we are not
3209
while not search.is_empty() and len(sources) > 1:
3210
source = sources.pop(0)
3211
stream = self._get_stream(source, search)
3212
for kind, substream in stream:
3213
if kind != 'revisions':
3214
yield kind, substream
3216
yield kind, self.missing_parents_rev_handler(substream)
3217
search = search.refine(self.seen_revs, self.referenced_revs)
3218
self.seen_revs = set()
3219
self.referenced_revs = set()
3220
if not search.is_empty():
3221
for kind, stream in self._get_stream(sources[0], search):
3224
def missing_parents_rev_handler(self, substream):
3225
for content in substream:
3226
revision_bytes = content.get_bytes_as('fulltext')
3227
revision = self.from_serialiser.read_revision_from_string(
3229
self.seen_revs.add(content.key[-1])
3230
self.referenced_revs.update(revision.parent_ids)
3234
class RemoteBranchLockableFiles(LockableFiles):
3235
"""A 'LockableFiles' implementation that talks to a smart server.
3237
This is not a public interface class.
3240
def __init__(self, bzrdir, _client):
3241
self.controldir = bzrdir
3242
self._client = _client
3243
self._need_find_modes = True
3244
LockableFiles.__init__(
3245
self, bzrdir.get_branch_transport(None),
3246
'lock', lockdir.LockDir)
3248
def _find_modes(self):
3249
# RemoteBranches don't let the client set the mode of control files.
3250
self._dir_mode = None
3251
self._file_mode = None
3254
class RemoteBranchFormat(branch.BranchFormat):
3256
def __init__(self, network_name=None):
3257
super(RemoteBranchFormat, self).__init__()
3258
self._matchingcontroldir = RemoteBzrDirFormat()
3259
self._matchingcontroldir.set_branch_format(self)
3260
self._custom_format = None
3261
self._network_name = network_name
3263
def __eq__(self, other):
3264
return (isinstance(other, RemoteBranchFormat)
3265
and self.__dict__ == other.__dict__)
3267
def _ensure_real(self):
3268
if self._custom_format is None:
3270
self._custom_format = branch.network_format_registry.get(
3273
raise errors.UnknownFormatError(kind='branch',
3274
format=self._network_name)
3276
def get_format_description(self):
3278
return 'Remote: ' + self._custom_format.get_format_description()
3280
def network_name(self):
3281
return self._network_name
3283
def open(self, a_controldir, name=None, ignore_fallbacks=False):
3284
return a_controldir.open_branch(name=name,
3285
ignore_fallbacks=ignore_fallbacks)
3287
def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3289
# Initialisation when using a local bzrdir object, or a non-vfs init
3290
# method is not available on the server.
3291
# self._custom_format is always set - the start of initialize ensures
3293
if isinstance(a_controldir, RemoteBzrDir):
3294
a_controldir._ensure_real()
3295
result = self._custom_format.initialize(a_controldir._real_bzrdir,
3296
name=name, append_revisions_only=append_revisions_only,
3297
repository=repository)
3299
# We assume the bzrdir is parameterised; it may not be.
3300
result = self._custom_format.initialize(a_controldir, name=name,
3301
append_revisions_only=append_revisions_only,
3302
repository=repository)
3303
if (isinstance(a_controldir, RemoteBzrDir)
3304
and not isinstance(result, RemoteBranch)):
3305
result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
3309
def initialize(self, a_controldir, name=None, repository=None,
3310
append_revisions_only=None):
3312
name = a_controldir._get_selected_branch()
3313
# 1) get the network name to use.
3314
if self._custom_format:
3315
network_name = self._custom_format.network_name()
3317
# Select the current breezy default and ask for that.
3318
reference_bzrdir_format = controldir.format_registry.get(
3320
reference_format = reference_bzrdir_format.get_branch_format()
3321
self._custom_format = reference_format
3322
network_name = reference_format.network_name()
3323
# Being asked to create on a non RemoteBzrDir:
3324
if not isinstance(a_controldir, RemoteBzrDir):
3325
return self._vfs_initialize(a_controldir, name=name,
3326
append_revisions_only=append_revisions_only,
3327
repository=repository)
3328
medium = a_controldir._client._medium
3329
if medium._is_remote_before((1, 13)):
3330
return self._vfs_initialize(a_controldir, name=name,
3331
append_revisions_only=append_revisions_only,
3332
repository=repository)
3333
# Creating on a remote bzr dir.
3334
# 2) try direct creation via RPC
3335
path = a_controldir._path_for_remote_call(a_controldir._client)
3337
# XXX JRV20100304: Support creating colocated branches
3338
raise errors.NoColocatedBranchSupport(self)
3339
verb = b'BzrDir.create_branch'
3341
response = a_controldir._call(verb, path, network_name)
3342
except errors.UnknownSmartMethod:
3343
# Fallback - use vfs methods
3344
medium._remember_remote_is_before((1, 13))
3345
return self._vfs_initialize(a_controldir, name=name,
3346
append_revisions_only=append_revisions_only,
3347
repository=repository)
3348
if response[0] != b'ok':
3349
raise errors.UnexpectedSmartServerResponse(response)
3350
# Turn the response into a RemoteRepository object.
3351
format = RemoteBranchFormat(network_name=response[1])
3352
repo_format = response_tuple_to_repo_format(response[3:])
3353
repo_path = response[2].decode('utf-8')
3354
if repository is not None:
3355
remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3356
url_diff = urlutils.relative_url(repository.user_url,
3359
raise AssertionError(
3360
'repository.user_url %r does not match URL from server '
3361
'response (%r + %r)'
3362
% (repository.user_url, a_controldir.user_url, repo_path))
3363
remote_repo = repository
3366
repo_bzrdir = a_controldir
3368
repo_bzrdir = RemoteBzrDir(
3369
a_controldir.root_transport.clone(
3370
repo_path), a_controldir._format,
3371
a_controldir._client)
3372
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3373
remote_branch = RemoteBranch(a_controldir, remote_repo,
3374
format=format, setup_stacking=False, name=name)
3375
if append_revisions_only:
3376
remote_branch.set_append_revisions_only(append_revisions_only)
3377
# XXX: We know this is a new branch, so it must have revno 0, revid
3378
# NULL_REVISION. Creating the branch locked would make this be unable
3379
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3380
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3381
return remote_branch
3383
def make_tags(self, branch):
3385
return self._custom_format.make_tags(branch)
3387
def supports_tags(self):
3388
# Remote branches might support tags, but we won't know until we
3389
# access the real remote branch.
3391
return self._custom_format.supports_tags()
3393
def supports_stacking(self):
3395
return self._custom_format.supports_stacking()
3397
def supports_set_append_revisions_only(self):
3399
return self._custom_format.supports_set_append_revisions_only()
3401
def _use_default_local_heads_to_fetch(self):
3402
# If the branch format is a metadir format *and* its heads_to_fetch
3403
# implementation is not overridden vs the base class, we can use the
3404
# base class logic rather than use the heads_to_fetch RPC. This is
3405
# usually cheaper in terms of net round trips, as the last-revision and
3406
# tags info fetched is cached and would be fetched anyway.
3408
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3409
branch_class = self._custom_format._branch_class()
3410
heads_to_fetch_impl = get_unbound_function(
3411
branch_class.heads_to_fetch)
3412
if heads_to_fetch_impl is get_unbound_function(branch.Branch.heads_to_fetch):
3417
class RemoteBranchStore(_mod_config.IniFileStore):
3418
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3420
Note that this is specific to bzr-based formats.
3423
def __init__(self, branch):
3424
super(RemoteBranchStore, self).__init__()
3425
self.branch = branch
3427
self._real_store = None
3429
def external_url(self):
3430
return urlutils.join(self.branch.user_url, 'branch.conf')
3432
def _load_content(self):
3433
path = self.branch._remote_path()
3435
response, handler = self.branch._call_expecting_body(
3436
b'Branch.get_config_file', path)
3437
except errors.UnknownSmartMethod:
3439
return self._real_store._load_content()
3440
if len(response) and response[0] != b'ok':
3441
raise errors.UnexpectedSmartServerResponse(response)
3442
return handler.read_body_bytes()
3444
def _save_content(self, content):
3445
path = self.branch._remote_path()
3447
response, handler = self.branch._call_with_body_bytes_expecting_body(
3448
b'Branch.put_config_file', (path,
3449
self.branch._lock_token, self.branch._repo_lock_token),
3451
except errors.UnknownSmartMethod:
3453
return self._real_store._save_content(content)
3454
handler.cancel_read_body()
3455
if response != (b'ok', ):
3456
raise errors.UnexpectedSmartServerResponse(response)
3458
def _ensure_real(self):
3459
self.branch._ensure_real()
3460
if self._real_store is None:
3461
self._real_store = _mod_config.BranchStore(self.branch)
3464
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3465
"""Branch stored on a server accessed by HPSS RPC.
3467
At the moment most operations are mapped down to simple file operations.
3470
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3471
_client=None, format=None, setup_stacking=True, name=None,
3472
possible_transports=None):
3473
"""Create a RemoteBranch instance.
3475
:param real_branch: An optional local implementation of the branch
3476
format, usually accessing the data via the VFS.
3477
:param _client: Private parameter for testing.
3478
:param format: A RemoteBranchFormat object, None to create one
3479
automatically. If supplied it should have a network_name already
3481
:param setup_stacking: If True make an RPC call to determine the
3482
stacked (or not) status of the branch. If False assume the branch
3484
:param name: Colocated branch name
3486
# We intentionally don't call the parent class's __init__, because it
3487
# will try to assign to self.tags, which is a property in this subclass.
3488
# And the parent's __init__ doesn't do much anyway.
3489
self.controldir = remote_bzrdir
3491
if _client is not None:
3492
self._client = _client
3494
self._client = remote_bzrdir._client
3495
self.repository = remote_repository
3496
if real_branch is not None:
3497
self._real_branch = real_branch
3498
# Give the remote repository the matching real repo.
3499
real_repo = self._real_branch.repository
3500
if isinstance(real_repo, RemoteRepository):
3501
real_repo._ensure_real()
3502
real_repo = real_repo._real_repository
3503
self.repository._set_real_repository(real_repo)
3504
# Give the branch the remote repository to let fast-pathing happen.
3505
self._real_branch.repository = self.repository
3507
self._real_branch = None
3508
# Fill out expected attributes of branch for breezy API users.
3509
self._clear_cached_state()
3510
# TODO: deprecate self.base in favor of user_url
3511
self.base = self.controldir.user_url
3513
self._control_files = None
3514
self._lock_mode = None
3515
self._lock_token = None
3516
self._repo_lock_token = None
3517
self._lock_count = 0
3518
self._leave_lock = False
3519
self.conf_store = None
3520
# Setup a format: note that we cannot call _ensure_real until all the
3521
# attributes above are set: This code cannot be moved higher up in this
3524
self._format = RemoteBranchFormat()
3525
if real_branch is not None:
3526
self._format._network_name = \
3527
self._real_branch._format.network_name()
3529
self._format = format
3530
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3531
# branch.open_branch method.
3532
self._real_ignore_fallbacks = not setup_stacking
3533
if not self._format._network_name:
3534
# Did not get from open_branchV2 - old server.
3536
self._format._network_name = \
3537
self._real_branch._format.network_name()
3538
self.tags = self._format.make_tags(self)
3539
# The base class init is not called, so we duplicate this:
3540
hooks = branch.Branch.hooks['open']
3543
self._is_stacked = False
3545
self._setup_stacking(possible_transports)
3547
def _setup_stacking(self, possible_transports):
3548
# configure stacking into the remote repository, by reading it from
3551
fallback_url = self.get_stacked_on_url()
3552
except (errors.NotStacked, branch.UnstackableBranchFormat,
3553
errors.UnstackableRepositoryFormat) as e:
3555
self._is_stacked = True
3556
if possible_transports is None:
3557
possible_transports = []
3559
possible_transports = list(possible_transports)
3560
possible_transports.append(self.controldir.root_transport)
3561
self._activate_fallback_location(fallback_url,
3562
possible_transports=possible_transports)
3564
def _get_config(self):
3565
return RemoteBranchConfig(self)
3567
def _get_config_store(self):
3568
if self.conf_store is None:
3569
self.conf_store = RemoteBranchStore(self)
3570
return self.conf_store
3572
def store_uncommitted(self, creator):
3574
return self._real_branch.store_uncommitted(creator)
3576
def get_unshelver(self, tree):
3578
return self._real_branch.get_unshelver(tree)
3580
def _get_real_transport(self):
3581
# if we try vfs access, return the real branch's vfs transport
3583
return self._real_branch._transport
3585
_transport = property(_get_real_transport)
3588
return "%s(%s)" % (self.__class__.__name__, self.base)
3592
def _ensure_real(self):
3593
"""Ensure that there is a _real_branch set.
3595
Used before calls to self._real_branch.
3597
if self._real_branch is None:
3598
if not vfs.vfs_enabled():
3599
raise AssertionError('smart server vfs must be enabled '
3600
'to use vfs implementation')
3601
self.controldir._ensure_real()
3602
self._real_branch = self.controldir._real_bzrdir.open_branch(
3603
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3604
# The remote branch and the real branch shares the same store. If
3605
# we don't, there will always be cases where one of the stores
3606
# doesn't see an update made on the other.
3607
self._real_branch.conf_store = self.conf_store
3608
if self.repository._real_repository is None:
3609
# Give the remote repository the matching real repo.
3610
real_repo = self._real_branch.repository
3611
if isinstance(real_repo, RemoteRepository):
3612
real_repo._ensure_real()
3613
real_repo = real_repo._real_repository
3614
self.repository._set_real_repository(real_repo)
3615
# Give the real branch the remote repository to let fast-pathing
3617
self._real_branch.repository = self.repository
3618
if self._lock_mode == 'r':
3619
self._real_branch.lock_read()
3620
elif self._lock_mode == 'w':
3621
self._real_branch.lock_write(token=self._lock_token)
3623
def _translate_error(self, err, **context):
3624
self.repository._translate_error(err, branch=self, **context)
3626
def _clear_cached_state(self):
3627
super(RemoteBranch, self)._clear_cached_state()
3628
self._tags_bytes = None
3629
if self._real_branch is not None:
3630
self._real_branch._clear_cached_state()
3632
def _clear_cached_state_of_remote_branch_only(self):
3633
"""Like _clear_cached_state, but doesn't clear the cache of
3636
This is useful when falling back to calling a method of
3637
self._real_branch that changes state. In that case the underlying
3638
branch changes, so we need to invalidate this RemoteBranch's cache of
3639
it. However, there's no need to invalidate the _real_branch's cache
3640
too, in fact doing so might harm performance.
3642
super(RemoteBranch, self)._clear_cached_state()
3645
def control_files(self):
3646
# Defer actually creating RemoteBranchLockableFiles until its needed,
3647
# because it triggers an _ensure_real that we otherwise might not need.
3648
if self._control_files is None:
3649
self._control_files = RemoteBranchLockableFiles(
3650
self.controldir, self._client)
3651
return self._control_files
3653
def get_physical_lock_status(self):
3654
"""See Branch.get_physical_lock_status()."""
3656
response = self._client.call(b'Branch.get_physical_lock_status',
3657
self._remote_path())
3658
except errors.UnknownSmartMethod:
3660
return self._real_branch.get_physical_lock_status()
3661
if response[0] not in (b'yes', b'no'):
3662
raise errors.UnexpectedSmartServerResponse(response)
3663
return (response[0] == b'yes')
3665
def get_stacked_on_url(self):
3666
"""Get the URL this branch is stacked against.
3668
:raises NotStacked: If the branch is not stacked.
3669
:raises UnstackableBranchFormat: If the branch does not support
3671
:raises UnstackableRepositoryFormat: If the repository does not support
3675
# there may not be a repository yet, so we can't use
3676
# self._translate_error, so we can't use self._call either.
3677
response = self._client.call(b'Branch.get_stacked_on_url',
3678
self._remote_path())
3679
except errors.ErrorFromSmartServer as err:
3680
# there may not be a repository yet, so we can't call through
3681
# its _translate_error
3682
_translate_error(err, branch=self)
3683
except errors.UnknownSmartMethod as err:
3685
return self._real_branch.get_stacked_on_url()
3686
if response[0] != b'ok':
3687
raise errors.UnexpectedSmartServerResponse(response)
3688
if sys.version_info[0] == 3:
3689
return response[1].decode('utf-8')
3692
def set_stacked_on_url(self, url):
3693
branch.Branch.set_stacked_on_url(self, url)
3694
# We need the stacked_on_url to be visible both locally (to not query
3695
# it repeatedly) and remotely (so smart verbs can get it server side)
3696
# Without the following line,
3697
# breezy.tests.per_branch.test_create_clone.TestCreateClone
3698
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3699
# fails for remote branches -- vila 2012-01-04
3700
self.conf_store.save_changes()
3702
self._is_stacked = False
3704
self._is_stacked = True
3706
def _vfs_get_tags_bytes(self):
3708
return self._real_branch._get_tags_bytes()
3710
def _get_tags_bytes(self):
3711
with self.lock_read():
3712
if self._tags_bytes is None:
3713
self._tags_bytes = self._get_tags_bytes_via_hpss()
3714
return self._tags_bytes
3716
def _get_tags_bytes_via_hpss(self):
3717
medium = self._client._medium
3718
if medium._is_remote_before((1, 13)):
3719
return self._vfs_get_tags_bytes()
3721
response = self._call(
3722
b'Branch.get_tags_bytes', self._remote_path())
3723
except errors.UnknownSmartMethod:
3724
medium._remember_remote_is_before((1, 13))
3725
return self._vfs_get_tags_bytes()
3728
def _vfs_set_tags_bytes(self, bytes):
3730
return self._real_branch._set_tags_bytes(bytes)
3732
def _set_tags_bytes(self, bytes):
3733
if self.is_locked():
3734
self._tags_bytes = bytes
3735
medium = self._client._medium
3736
if medium._is_remote_before((1, 18)):
3737
self._vfs_set_tags_bytes(bytes)
3741
self._remote_path(), self._lock_token, self._repo_lock_token)
3742
response = self._call_with_body_bytes(
3743
b'Branch.set_tags_bytes', args, bytes)
3744
except errors.UnknownSmartMethod:
3745
medium._remember_remote_is_before((1, 18))
3746
self._vfs_set_tags_bytes(bytes)
3748
def lock_read(self):
3749
"""Lock the branch for read operations.
3751
:return: A breezy.lock.LogicalLockResult.
3753
self.repository.lock_read()
3754
if not self._lock_mode:
3755
self._note_lock('r')
3756
self._lock_mode = 'r'
3757
self._lock_count = 1
3758
if self._real_branch is not None:
3759
self._real_branch.lock_read()
3761
self._lock_count += 1
3762
return lock.LogicalLockResult(self.unlock)
3764
def _remote_lock_write(self, token):
3766
branch_token = repo_token = b''
3768
branch_token = token
3769
repo_token = self.repository.lock_write().repository_token
3770
self.repository.unlock()
3771
err_context = {'token': token}
3773
response = self._call(
3774
b'Branch.lock_write', self._remote_path(), branch_token,
3775
repo_token or b'', **err_context)
3776
except errors.LockContention as e:
3777
# The LockContention from the server doesn't have any
3778
# information about the lock_url. We re-raise LockContention
3779
# with valid lock_url.
3780
raise errors.LockContention('(remote lock)',
3781
self.repository.base.split('.bzr/')[0])
3782
if response[0] != b'ok':
3783
raise errors.UnexpectedSmartServerResponse(response)
3784
ok, branch_token, repo_token = response
3785
return branch_token, repo_token
3787
def lock_write(self, token=None):
3788
if not self._lock_mode:
3789
self._note_lock('w')
3790
# Lock the branch and repo in one remote call.
3791
remote_tokens = self._remote_lock_write(token)
3792
self._lock_token, self._repo_lock_token = remote_tokens
3793
if not self._lock_token:
3794
raise SmartProtocolError(
3795
'Remote server did not return a token!')
3796
# Tell the self.repository object that it is locked.
3797
self.repository.lock_write(
3798
self._repo_lock_token, _skip_rpc=True)
3800
if self._real_branch is not None:
3801
self._real_branch.lock_write(token=self._lock_token)
3802
if token is not None:
3803
self._leave_lock = True
3805
self._leave_lock = False
3806
self._lock_mode = 'w'
3807
self._lock_count = 1
3808
elif self._lock_mode == 'r':
3809
raise errors.ReadOnlyError(self)
3811
if token is not None:
3812
# A token was given to lock_write, and we're relocking, so
3813
# check that the given token actually matches the one we
3815
if token != self._lock_token:
3816
raise errors.TokenMismatch(token, self._lock_token)
3817
self._lock_count += 1
3818
# Re-lock the repository too.
3819
self.repository.lock_write(self._repo_lock_token)
3820
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3822
def _unlock(self, branch_token, repo_token):
3823
err_context = {'token': str((branch_token, repo_token))}
3824
response = self._call(
3825
b'Branch.unlock', self._remote_path(), branch_token,
3826
repo_token or b'', **err_context)
3827
if response == (b'ok',):
3829
raise errors.UnexpectedSmartServerResponse(response)
3831
@only_raises(errors.LockNotHeld, errors.LockBroken)
3834
self._lock_count -= 1
3835
if not self._lock_count:
3836
if self.conf_store is not None:
3837
self.conf_store.save_changes()
3838
self._clear_cached_state()
3839
mode = self._lock_mode
3840
self._lock_mode = None
3841
if self._real_branch is not None:
3842
if (not self._leave_lock and mode == 'w'
3843
and self._repo_lock_token):
3844
# If this RemoteBranch will remove the physical lock
3845
# for the repository, make sure the _real_branch
3846
# doesn't do it first. (Because the _real_branch's
3847
# repository is set to be the RemoteRepository.)
3848
self._real_branch.repository.leave_lock_in_place()
3849
self._real_branch.unlock()
3851
# Only write-locked branched need to make a remote method
3852
# call to perform the unlock.
3854
if not self._lock_token:
3855
raise AssertionError('Locked, but no token!')
3856
branch_token = self._lock_token
3857
repo_token = self._repo_lock_token
3858
self._lock_token = None
3859
self._repo_lock_token = None
3860
if not self._leave_lock:
3861
self._unlock(branch_token, repo_token)
3863
self.repository.unlock()
3865
def break_lock(self):
3867
response = self._call(
3868
b'Branch.break_lock', self._remote_path())
3869
except errors.UnknownSmartMethod:
3871
return self._real_branch.break_lock()
3872
if response != (b'ok',):
3873
raise errors.UnexpectedSmartServerResponse(response)
3875
def leave_lock_in_place(self):
3876
if not self._lock_token:
3877
raise NotImplementedError(self.leave_lock_in_place)
3878
self._leave_lock = True
3880
def dont_leave_lock_in_place(self):
3881
if not self._lock_token:
3882
raise NotImplementedError(self.dont_leave_lock_in_place)
3883
self._leave_lock = False
3885
def get_rev_id(self, revno, history=None):
3887
return _mod_revision.NULL_REVISION
3888
with self.lock_read():
3889
last_revision_info = self.last_revision_info()
3891
raise errors.RevnoOutOfBounds(
3892
revno, (0, last_revision_info[0]))
3893
ok, result = self.repository.get_rev_id_for_revno(
3894
revno, last_revision_info)
3897
missing_parent = result[1]
3898
# Either the revision named by the server is missing, or its parent
3899
# is. Call get_parent_map to determine which, so that we report a
3901
parent_map = self.repository.get_parent_map([missing_parent])
3902
if missing_parent in parent_map:
3903
missing_parent = parent_map[missing_parent]
3904
raise errors.NoSuchRevision(self, missing_parent)
3906
def _read_last_revision_info(self):
3907
response = self._call(
3908
b'Branch.last_revision_info', self._remote_path())
3909
if response[0] != b'ok':
3910
raise SmartProtocolError(
3911
'unexpected response code %s' % (response,))
3912
revno = int(response[1])
3913
last_revision = response[2]
3914
return (revno, last_revision)
3916
def _gen_revision_history(self):
3917
"""See Branch._gen_revision_history()."""
3918
if self._is_stacked:
3920
return self._real_branch._gen_revision_history()
3921
response_tuple, response_handler = self._call_expecting_body(
3922
b'Branch.revision_history', self._remote_path())
3923
if response_tuple[0] != b'ok':
3924
raise errors.UnexpectedSmartServerResponse(response_tuple)
3925
result = response_handler.read_body_bytes().split(b'\x00')
3930
def _remote_path(self):
3931
return self.controldir._path_for_remote_call(self._client)
3933
def _set_last_revision_descendant(self, revision_id, other_branch,
3934
allow_diverged=False, allow_overwrite_descendant=False):
3935
# This performs additional work to meet the hook contract; while its
3936
# undesirable, we have to synthesise the revno to call the hook, and
3937
# not calling the hook is worse as it means changes can't be prevented.
3938
# Having calculated this though, we can't just call into
3939
# set_last_revision_info as a simple call, because there is a set_rh
3940
# hook that some folk may still be using.
3941
old_revno, old_revid = self.last_revision_info()
3942
history = self._lefthand_history(revision_id)
3943
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3944
err_context = {'other_branch': other_branch}
3945
response = self._call(b'Branch.set_last_revision_ex',
3946
self._remote_path(), self._lock_token, self._repo_lock_token,
3947
revision_id, int(allow_diverged), int(
3948
allow_overwrite_descendant),
3950
self._clear_cached_state()
3951
if len(response) != 3 and response[0] != b'ok':
3952
raise errors.UnexpectedSmartServerResponse(response)
3953
new_revno, new_revision_id = response[1:]
3954
self._last_revision_info_cache = new_revno, new_revision_id
3955
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3956
if self._real_branch is not None:
3957
cache = new_revno, new_revision_id
3958
self._real_branch._last_revision_info_cache = cache
3960
def _set_last_revision(self, revision_id):
3961
old_revno, old_revid = self.last_revision_info()
3962
# This performs additional work to meet the hook contract; while its
3963
# undesirable, we have to synthesise the revno to call the hook, and
3964
# not calling the hook is worse as it means changes can't be prevented.
3965
# Having calculated this though, we can't just call into
3966
# set_last_revision_info as a simple call, because there is a set_rh
3967
# hook that some folk may still be using.
3968
history = self._lefthand_history(revision_id)
3969
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3970
self._clear_cached_state()
3971
response = self._call(b'Branch.set_last_revision',
3972
self._remote_path(), self._lock_token, self._repo_lock_token,
3974
if response != (b'ok',):
3975
raise errors.UnexpectedSmartServerResponse(response)
3976
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3978
def _get_parent_location(self):
3979
medium = self._client._medium
3980
if medium._is_remote_before((1, 13)):
3981
return self._vfs_get_parent_location()
3983
response = self._call(b'Branch.get_parent', self._remote_path())
3984
except errors.UnknownSmartMethod:
3985
medium._remember_remote_is_before((1, 13))
3986
return self._vfs_get_parent_location()
3987
if len(response) != 1:
3988
raise errors.UnexpectedSmartServerResponse(response)
3989
parent_location = response[0]
3990
if parent_location == b'':
3992
return parent_location.decode('utf-8')
3994
def _vfs_get_parent_location(self):
3996
return self._real_branch._get_parent_location()
3998
def _set_parent_location(self, url):
3999
medium = self._client._medium
4000
if medium._is_remote_before((1, 15)):
4001
return self._vfs_set_parent_location(url)
4003
call_url = url or u''
4004
if isinstance(call_url, text_type):
4005
call_url = call_url.encode('utf-8')
4006
response = self._call(b'Branch.set_parent_location',
4007
self._remote_path(), self._lock_token, self._repo_lock_token,
4009
except errors.UnknownSmartMethod:
4010
medium._remember_remote_is_before((1, 15))
4011
return self._vfs_set_parent_location(url)
4013
raise errors.UnexpectedSmartServerResponse(response)
4015
def _vfs_set_parent_location(self, url):
4017
return self._real_branch._set_parent_location(url)
4019
def pull(self, source, overwrite=False, stop_revision=None,
4021
with self.lock_write():
4022
self._clear_cached_state_of_remote_branch_only()
4024
return self._real_branch.pull(
4025
source, overwrite=overwrite, stop_revision=stop_revision,
4026
_override_hook_target=self, **kwargs)
4028
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
4029
with self.lock_read():
4031
return self._real_branch.push(
4032
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
4033
_override_hook_source_branch=self)
4035
def peek_lock_mode(self):
4036
return self._lock_mode
4038
def is_locked(self):
4039
return self._lock_count >= 1
4041
def revision_id_to_dotted_revno(self, revision_id):
4042
"""Given a revision id, return its dotted revno.
4044
:return: a tuple like (1,) or (400,1,3).
4046
with self.lock_read():
4048
response = self._call(b'Branch.revision_id_to_revno',
4049
self._remote_path(), revision_id)
4050
except errors.UnknownSmartMethod:
4052
return self._real_branch.revision_id_to_dotted_revno(revision_id)
4053
except errors.UnknownErrorFromSmartServer as e:
4054
# Deal with older versions of bzr/brz that didn't explicitly
4055
# wrap GhostRevisionsHaveNoRevno.
4056
if e.error_tuple[1] == b'GhostRevisionsHaveNoRevno':
4057
(revid, ghost_revid) = re.findall(b"{([^}]+)}", e.error_tuple[2])
4058
raise errors.GhostRevisionsHaveNoRevno(
4061
if response[0] == b'ok':
4062
return tuple([int(x) for x in response[1:]])
4064
raise errors.UnexpectedSmartServerResponse(response)
4066
def revision_id_to_revno(self, revision_id):
4067
"""Given a revision id on the branch mainline, return its revno.
4071
with self.lock_read():
4073
response = self._call(b'Branch.revision_id_to_revno',
4074
self._remote_path(), revision_id)
4075
except errors.UnknownSmartMethod:
4077
return self._real_branch.revision_id_to_revno(revision_id)
4078
if response[0] == b'ok':
4079
if len(response) == 2:
4080
return int(response[1])
4081
raise NoSuchRevision(self, revision_id)
4083
raise errors.UnexpectedSmartServerResponse(response)
4085
def set_last_revision_info(self, revno, revision_id):
4086
with self.lock_write():
4087
# XXX: These should be returned by the set_last_revision_info verb
4088
old_revno, old_revid = self.last_revision_info()
4089
self._run_pre_change_branch_tip_hooks(revno, revision_id)
4090
if not revision_id or not isinstance(revision_id, bytes):
4091
raise errors.InvalidRevisionId(
4092
revision_id=revision_id, branch=self)
4094
response = self._call(b'Branch.set_last_revision_info',
4095
self._remote_path(), self._lock_token, self._repo_lock_token,
4096
str(revno).encode('ascii'), revision_id)
4097
except errors.UnknownSmartMethod:
4099
self._clear_cached_state_of_remote_branch_only()
4100
self._real_branch.set_last_revision_info(revno, revision_id)
4101
self._last_revision_info_cache = revno, revision_id
4103
if response == (b'ok',):
4104
self._clear_cached_state()
4105
self._last_revision_info_cache = revno, revision_id
4106
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
4107
# Update the _real_branch's cache too.
4108
if self._real_branch is not None:
4109
cache = self._last_revision_info_cache
4110
self._real_branch._last_revision_info_cache = cache
4112
raise errors.UnexpectedSmartServerResponse(response)
4114
def generate_revision_history(self, revision_id, last_rev=None,
4116
with self.lock_write():
4117
medium = self._client._medium
4118
if not medium._is_remote_before((1, 6)):
4119
# Use a smart method for 1.6 and above servers
4121
self._set_last_revision_descendant(revision_id, other_branch,
4122
allow_diverged=True, allow_overwrite_descendant=True)
4124
except errors.UnknownSmartMethod:
4125
medium._remember_remote_is_before((1, 6))
4126
self._clear_cached_state_of_remote_branch_only()
4127
graph = self.repository.get_graph()
4128
(last_revno, last_revid) = self.last_revision_info()
4129
known_revision_ids = [
4130
(last_revid, last_revno),
4131
(_mod_revision.NULL_REVISION, 0),
4133
if last_rev is not None:
4134
if not graph.is_ancestor(last_rev, revision_id):
4135
# our previous tip is not merged into stop_revision
4136
raise errors.DivergedBranches(self, other_branch)
4137
revno = graph.find_distance_to_null(
4138
revision_id, known_revision_ids)
4139
self.set_last_revision_info(revno, revision_id)
4141
def set_push_location(self, location):
4142
self._set_config_location('push_location', location)
4144
def heads_to_fetch(self):
4145
if self._format._use_default_local_heads_to_fetch():
4146
# We recognise this format, and its heads-to-fetch implementation
4147
# is the default one (tip + tags). In this case it's cheaper to
4148
# just use the default implementation rather than a special RPC as
4149
# the tip and tags data is cached.
4150
return branch.Branch.heads_to_fetch(self)
4151
medium = self._client._medium
4152
if medium._is_remote_before((2, 4)):
4153
return self._vfs_heads_to_fetch()
4155
return self._rpc_heads_to_fetch()
4156
except errors.UnknownSmartMethod:
4157
medium._remember_remote_is_before((2, 4))
4158
return self._vfs_heads_to_fetch()
4160
def _rpc_heads_to_fetch(self):
4161
response = self._call(b'Branch.heads_to_fetch', self._remote_path())
4162
if len(response) != 2:
4163
raise errors.UnexpectedSmartServerResponse(response)
4164
must_fetch, if_present_fetch = response
4165
return set(must_fetch), set(if_present_fetch)
4167
def _vfs_heads_to_fetch(self):
4169
return self._real_branch.heads_to_fetch()
4171
def reconcile(self, thorough=True):
4172
"""Make sure the data stored in this branch is consistent."""
4173
from .reconcile import BranchReconciler
4174
with self.lock_write():
4175
reconciler = BranchReconciler(self, thorough=thorough)
4176
return reconciler.reconcile()
4179
class RemoteConfig(object):
4180
"""A Config that reads and writes from smart verbs.
4182
It is a low-level object that considers config data to be name/value pairs
4183
that may be associated with a section. Assigning meaning to the these
4184
values is done at higher levels like breezy.config.TreeConfig.
4187
def get_option(self, name, section=None, default=None):
4188
"""Return the value associated with a named option.
4190
:param name: The name of the value
4191
:param section: The section the option is in (if any)
4192
:param default: The value to return if the value is not set
4193
:return: The value or default value
4196
configobj = self._get_configobj()
4199
section_obj = configobj
4202
section_obj = configobj[section]
4205
if section_obj is None:
4208
value = section_obj.get(name, default)
4209
except errors.UnknownSmartMethod:
4210
value = self._vfs_get_option(name, section, default)
4211
for hook in _mod_config.OldConfigHooks['get']:
4212
hook(self, name, value)
4215
def _response_to_configobj(self, response):
4216
if len(response[0]) and response[0][0] != b'ok':
4217
raise errors.UnexpectedSmartServerResponse(response)
4218
lines = response[1].read_body_bytes().splitlines()
4219
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4220
for hook in _mod_config.OldConfigHooks['load']:
4225
class RemoteBranchConfig(RemoteConfig):
4226
"""A RemoteConfig for Branches."""
4228
def __init__(self, branch):
4229
self._branch = branch
4231
def _get_configobj(self):
4232
path = self._branch._remote_path()
4233
response = self._branch._client.call_expecting_body(
4234
b'Branch.get_config_file', path)
4235
return self._response_to_configobj(response)
4237
def set_option(self, value, name, section=None):
4238
"""Set the value associated with a named option.
4240
:param value: The value to set
4241
:param name: The name of the value to set
4242
:param section: The section the option is in (if any)
4244
medium = self._branch._client._medium
4245
if medium._is_remote_before((1, 14)):
4246
return self._vfs_set_option(value, name, section)
4247
if isinstance(value, dict):
4248
if medium._is_remote_before((2, 2)):
4249
return self._vfs_set_option(value, name, section)
4250
return self._set_config_option_dict(value, name, section)
4252
return self._set_config_option(value, name, section)
4254
def _set_config_option(self, value, name, section):
4255
if isinstance(value, (bool, int)):
4257
elif isinstance(value, (text_type, str)):
4260
raise TypeError(value)
4262
path = self._branch._remote_path()
4263
response = self._branch._client.call(b'Branch.set_config_option',
4264
path, self._branch._lock_token, self._branch._repo_lock_token,
4265
value.encode('utf-8'), name.encode('utf-8'),
4266
(section or '').encode('utf-8'))
4267
except errors.UnknownSmartMethod:
4268
medium = self._branch._client._medium
4269
medium._remember_remote_is_before((1, 14))
4270
return self._vfs_set_option(value, name, section)
4272
raise errors.UnexpectedSmartServerResponse(response)
4274
def _serialize_option_dict(self, option_dict):
4276
for key, value in option_dict.items():
4277
if isinstance(key, text_type):
4278
key = key.encode('utf8')
4279
if isinstance(value, text_type):
4280
value = value.encode('utf8')
4281
utf8_dict[key] = value
4282
return bencode.bencode(utf8_dict)
4284
def _set_config_option_dict(self, value, name, section):
4286
path = self._branch._remote_path()
4287
serialised_dict = self._serialize_option_dict(value)
4288
response = self._branch._client.call(
4289
b'Branch.set_config_option_dict',
4290
path, self._branch._lock_token, self._branch._repo_lock_token,
4291
serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
4292
except errors.UnknownSmartMethod:
4293
medium = self._branch._client._medium
4294
medium._remember_remote_is_before((2, 2))
4295
return self._vfs_set_option(value, name, section)
4297
raise errors.UnexpectedSmartServerResponse(response)
4299
def _real_object(self):
4300
self._branch._ensure_real()
4301
return self._branch._real_branch
4303
def _vfs_set_option(self, value, name, section=None):
4304
return self._real_object()._get_config().set_option(
4305
value, name, section)
4308
class RemoteBzrDirConfig(RemoteConfig):
4309
"""A RemoteConfig for BzrDirs."""
4311
def __init__(self, bzrdir):
4312
self._bzrdir = bzrdir
4314
def _get_configobj(self):
4315
medium = self._bzrdir._client._medium
4316
verb = b'BzrDir.get_config_file'
4317
if medium._is_remote_before((1, 15)):
4318
raise errors.UnknownSmartMethod(verb)
4319
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4320
response = self._bzrdir._call_expecting_body(
4322
return self._response_to_configobj(response)
4324
def _vfs_get_option(self, name, section, default):
4325
return self._real_object()._get_config().get_option(
4326
name, section, default)
4328
def set_option(self, value, name, section=None):
4329
"""Set the value associated with a named option.
4331
:param value: The value to set
4332
:param name: The name of the value to set
4333
:param section: The section the option is in (if any)
4335
return self._real_object()._get_config().set_option(
4336
value, name, section)
4338
def _real_object(self):
4339
self._bzrdir._ensure_real()
4340
return self._bzrdir._real_bzrdir
4343
error_translators = registry.Registry()
4344
no_context_error_translators = registry.Registry()
4347
def _translate_error(err, **context):
4348
"""Translate an ErrorFromSmartServer into a more useful error.
4350
Possible context keys:
4358
If the error from the server doesn't match a known pattern, then
4359
UnknownErrorFromSmartServer is raised.
4363
return context[name]
4365
mutter('Missing key \'%s\' in context %r', name, context)
4369
"""Get the path from the context if present, otherwise use first error
4373
return context['path']
4376
return err.error_args[0].decode('utf-8')
4378
mutter('Missing key \'path\' in context %r', context)
4380
if not isinstance(err.error_verb, bytes):
4381
raise TypeError(err.error_verb)
4383
translator = error_translators.get(err.error_verb)
4387
raise translator(err, find, get_path)
4389
translator = no_context_error_translators.get(err.error_verb)
4391
raise errors.UnknownErrorFromSmartServer(err)
4393
raise translator(err)
4396
error_translators.register(b'NoSuchRevision',
4397
lambda err, find, get_path: NoSuchRevision(
4398
find('branch'), err.error_args[0]))
4399
error_translators.register(b'nosuchrevision',
4400
lambda err, find, get_path: NoSuchRevision(
4401
find('repository'), err.error_args[0]))
4402
error_translators.register(
4403
b'revno-outofbounds',
4404
lambda err, find, get_path: errors.RevnoOutOfBounds(
4405
err.error_args[0], (err.error_args[1], err.error_args[2])))
4408
def _translate_nobranch_error(err, find, get_path):
4409
if len(err.error_args) >= 1:
4410
extra = err.error_args[0].decode('utf-8')
4413
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4417
error_translators.register(b'nobranch', _translate_nobranch_error)
4418
error_translators.register(b'norepository',
4419
lambda err, find, get_path: errors.NoRepositoryPresent(
4421
error_translators.register(b'UnlockableTransport',
4422
lambda err, find, get_path: errors.UnlockableTransport(
4423
find('bzrdir').root_transport))
4424
error_translators.register(b'TokenMismatch',
4425
lambda err, find, get_path: errors.TokenMismatch(
4426
find('token'), '(remote token)'))
4427
error_translators.register(b'Diverged',
4428
lambda err, find, get_path: errors.DivergedBranches(
4429
find('branch'), find('other_branch')))
4430
error_translators.register(b'NotStacked',
4431
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4434
def _translate_PermissionDenied(err, find, get_path):
4436
if len(err.error_args) >= 2:
4437
extra = err.error_args[1].decode('utf-8')
4440
return errors.PermissionDenied(path, extra=extra)
4443
error_translators.register(b'PermissionDenied', _translate_PermissionDenied)
4444
error_translators.register(b'ReadError',
4445
lambda err, find, get_path: errors.ReadError(get_path()))
4446
error_translators.register(b'NoSuchFile',
4447
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4448
error_translators.register(b'TokenLockingNotSupported',
4449
lambda err, find, get_path: errors.TokenLockingNotSupported(
4450
find('repository')))
4451
error_translators.register(b'UnsuspendableWriteGroup',
4452
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4453
repository=find('repository')))
4454
error_translators.register(b'UnresumableWriteGroup',
4455
lambda err, find, get_path: errors.UnresumableWriteGroup(
4456
repository=find('repository'), write_groups=err.error_args[0],
4457
reason=err.error_args[1]))
4458
no_context_error_translators.register(b'GhostRevisionsHaveNoRevno',
4459
lambda err: errors.GhostRevisionsHaveNoRevno(*err.error_args))
4460
no_context_error_translators.register(b'IncompatibleRepositories',
4461
lambda err: errors.IncompatibleRepositories(
4462
err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8'), err.error_args[2].decode('utf-8')))
4463
no_context_error_translators.register(b'LockContention',
4464
lambda err: errors.LockContention('(remote lock)'))
4465
no_context_error_translators.register(b'LockFailed',
4466
lambda err: errors.LockFailed(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4467
no_context_error_translators.register(b'TipChangeRejected',
4468
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4469
no_context_error_translators.register(b'UnstackableBranchFormat',
4470
lambda err: branch.UnstackableBranchFormat(*err.error_args))
4471
no_context_error_translators.register(b'UnstackableRepositoryFormat',
4472
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4473
no_context_error_translators.register(b'FileExists',
4474
lambda err: errors.FileExists(err.error_args[0].decode('utf-8')))
4475
no_context_error_translators.register(b'DirectoryNotEmpty',
4476
lambda err: errors.DirectoryNotEmpty(err.error_args[0].decode('utf-8')))
4477
no_context_error_translators.register(b'UnknownFormat',
4478
lambda err: errors.UnknownFormatError(
4479
err.error_args[0].decode('ascii'), err.error_args[0].decode('ascii')))
4480
no_context_error_translators.register(b'InvalidURL',
4481
lambda err: urlutils.InvalidURL(
4482
err.error_args[0].decode('utf-8'), err.error_args[1].decode('ascii')))
4485
def _translate_short_readv_error(err):
4486
args = err.error_args
4487
return errors.ShortReadvError(
4488
args[0].decode('utf-8'),
4489
int(args[1].decode('ascii')), int(args[2].decode('ascii')),
4490
int(args[3].decode('ascii')))
4493
no_context_error_translators.register(b'ShortReadvError',
4494
_translate_short_readv_error)
4497
def _translate_unicode_error(err):
4498
encoding = err.error_args[0].decode('ascii')
4499
val = err.error_args[1].decode('utf-8')
4500
start = int(err.error_args[2].decode('ascii'))
4501
end = int(err.error_args[3].decode('ascii'))
4502
reason = err.error_args[4].decode('utf-8')
4503
if val.startswith('u:'):
4504
val = val[2:].decode('utf-8')
4505
elif val.startswith('s:'):
4506
val = val[2:].decode('base64')
4507
if err.error_verb == 'UnicodeDecodeError':
4508
raise UnicodeDecodeError(encoding, val, start, end, reason)
4509
elif err.error_verb == 'UnicodeEncodeError':
4510
raise UnicodeEncodeError(encoding, val, start, end, reason)
4513
no_context_error_translators.register(b'UnicodeEncodeError',
4514
_translate_unicode_error)
4515
no_context_error_translators.register(b'UnicodeDecodeError',
4516
_translate_unicode_error)
4517
no_context_error_translators.register(b'ReadOnlyError',
4518
lambda err: errors.TransportNotPossible('readonly transport'))
4519
no_context_error_translators.register(b'MemoryError',
4520
lambda err: errors.BzrError("remote server out of memory\n"
4521
"Retry non-remotely, or contact the server admin for details."))
4522
no_context_error_translators.register(b'RevisionNotPresent',
4523
lambda err: errors.RevisionNotPresent(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4525
no_context_error_translators.register(b'BzrCheckError',
4526
lambda err: errors.BzrCheckError(msg=err.error_args[0].decode('utf-8')))