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
28
config as _mod_config,
38
repository as _mod_repository,
39
revision as _mod_revision,
40
testament as _mod_testament,
45
bzrdir as _mod_bzrdir,
50
from .branch import BranchReferenceFormat
51
from ..branch import BranchWriteLockResult
52
from ..decorators import only_raises
53
from ..errors import (
57
from ..i18n import gettext
58
from .inventory import Inventory
59
from .inventorytree import InventoryRevisionTree
60
from ..lockable_files import LockableFiles
61
from ..sixish import (
68
from .smart import client, vfs, repository as smart_repo
69
from .smart.client import _SmartClient
70
from ..revision import NULL_REVISION
71
from ..repository import RepositoryWriteLockResult, _LazyListJoin
72
from .serializer import format_registry as serializer_format_registry
73
from ..trace import mutter, note, warning, log_exception_quietly
74
from .versionedfile import FulltextContentFactory
77
_DEFAULT_SEARCH_DEPTH = 100
80
class _RpcHelper(object):
81
"""Mixin class that helps with issuing RPCs."""
83
def _call(self, method, *args, **err_context):
85
return self._client.call(method, *args)
86
except errors.ErrorFromSmartServer as err:
87
self._translate_error(err, **err_context)
89
def _call_expecting_body(self, method, *args, **err_context):
91
return self._client.call_expecting_body(method, *args)
92
except errors.ErrorFromSmartServer as err:
93
self._translate_error(err, **err_context)
95
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
97
return self._client.call_with_body_bytes(method, args, body_bytes)
98
except errors.ErrorFromSmartServer as err:
99
self._translate_error(err, **err_context)
101
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
104
return self._client.call_with_body_bytes_expecting_body(
105
method, args, body_bytes)
106
except errors.ErrorFromSmartServer as err:
107
self._translate_error(err, **err_context)
110
def response_tuple_to_repo_format(response):
111
"""Convert a response tuple describing a repository format to a format."""
112
format = RemoteRepositoryFormat()
113
format._rich_root_data = (response[0] == b'yes')
114
format._supports_tree_reference = (response[1] == b'yes')
115
format._supports_external_lookups = (response[2] == b'yes')
116
format._network_name = response[3]
120
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.bzr.remote
121
# does not have to be imported unless a remote format is involved.
123
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
124
"""Format representing bzrdirs accessed via a smart server"""
126
supports_workingtrees = False
128
colocated_branches = False
131
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
132
# XXX: It's a bit ugly that the network name is here, because we'd
133
# like to believe that format objects are stateless or at least
134
# immutable, However, we do at least avoid mutating the name after
135
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
136
self._network_name = None
139
return "%s(_network_name=%r)" % (self.__class__.__name__,
142
def get_format_description(self):
143
if self._network_name:
145
real_format = controldir.network_format_registry.get(
150
return 'Remote: ' + real_format.get_format_description()
151
return 'bzr remote bzrdir'
153
def get_format_string(self):
154
raise NotImplementedError(self.get_format_string)
156
def network_name(self):
157
if self._network_name:
158
return self._network_name
160
raise AssertionError("No network name set.")
162
def initialize_on_transport(self, transport):
164
# hand off the request to the smart server
165
client_medium = transport.get_smart_medium()
166
except errors.NoSmartMedium:
167
# TODO: lookup the local format from a server hint.
168
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
169
return local_dir_format.initialize_on_transport(transport)
170
client = _SmartClient(client_medium)
171
path = client.remote_path_from_transport(transport)
173
response = client.call(b'BzrDirFormat.initialize', path)
174
except errors.ErrorFromSmartServer as err:
175
_translate_error(err, path=path)
176
if response[0] != b'ok':
177
raise errors.SmartProtocolError(
178
'unexpected response code %s' % (response,))
179
format = RemoteBzrDirFormat()
180
self._supply_sub_formats_to(format)
181
return RemoteBzrDir(transport, format)
183
def parse_NoneTrueFalse(self, arg):
190
raise AssertionError("invalid arg %r" % arg)
192
def _serialize_NoneTrueFalse(self, arg):
199
def _serialize_NoneString(self, arg):
202
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
203
create_prefix=False, force_new_repo=False, stacked_on=None,
204
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
207
# hand off the request to the smart server
208
client_medium = transport.get_smart_medium()
209
except errors.NoSmartMedium:
212
# Decline to open it if the server doesn't support our required
213
# version (3) so that the VFS-based transport will do it.
214
if client_medium.should_probe():
216
server_version = client_medium.protocol_version()
217
if server_version != '2':
221
except errors.SmartProtocolError:
222
# Apparently there's no usable smart server there, even though
223
# the medium supports the smart protocol.
228
client = _SmartClient(client_medium)
229
path = client.remote_path_from_transport(transport)
230
if client_medium._is_remote_before((1, 16)):
233
# TODO: lookup the local format from a server hint.
234
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
235
self._supply_sub_formats_to(local_dir_format)
236
return local_dir_format.initialize_on_transport_ex(transport,
237
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
238
force_new_repo=force_new_repo, stacked_on=stacked_on,
239
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
240
make_working_trees=make_working_trees, shared_repo=shared_repo,
242
return self._initialize_on_transport_ex_rpc(client, path, transport,
243
use_existing_dir, create_prefix, force_new_repo, stacked_on,
244
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
246
def _initialize_on_transport_ex_rpc(self, client, path, transport,
247
use_existing_dir, create_prefix, force_new_repo, stacked_on,
248
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
250
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
251
args.append(self._serialize_NoneTrueFalse(create_prefix))
252
args.append(self._serialize_NoneTrueFalse(force_new_repo))
253
args.append(self._serialize_NoneString(stacked_on))
254
# stack_on_pwd is often/usually our transport
257
stack_on_pwd = transport.relpath(stack_on_pwd).encode('utf-8')
260
except errors.PathNotChild:
262
args.append(self._serialize_NoneString(stack_on_pwd))
263
args.append(self._serialize_NoneString(repo_format_name))
264
args.append(self._serialize_NoneTrueFalse(make_working_trees))
265
args.append(self._serialize_NoneTrueFalse(shared_repo))
266
request_network_name = self._network_name or \
267
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
269
response = client.call(b'BzrDirFormat.initialize_ex_1.16',
270
request_network_name, path, *args)
271
except errors.UnknownSmartMethod:
272
client._medium._remember_remote_is_before((1, 16))
273
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
274
self._supply_sub_formats_to(local_dir_format)
275
return local_dir_format.initialize_on_transport_ex(transport,
276
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
277
force_new_repo=force_new_repo, stacked_on=stacked_on,
278
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
279
make_working_trees=make_working_trees, shared_repo=shared_repo,
281
except errors.ErrorFromSmartServer as err:
282
_translate_error(err, path=path.decode('utf-8'))
283
repo_path = response[0]
284
bzrdir_name = response[6]
285
require_stacking = response[7]
286
require_stacking = self.parse_NoneTrueFalse(require_stacking)
287
format = RemoteBzrDirFormat()
288
format._network_name = bzrdir_name
289
self._supply_sub_formats_to(format)
290
bzrdir = RemoteBzrDir(transport, format, _client=client)
292
repo_format = response_tuple_to_repo_format(response[1:])
293
if repo_path == b'.':
295
repo_path = repo_path.decode('utf-8')
297
repo_bzrdir_format = RemoteBzrDirFormat()
298
repo_bzrdir_format._network_name = response[5]
299
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
303
final_stack = response[8] or None
305
final_stack = final_stack.decode('utf-8')
306
final_stack_pwd = response[9] or None
308
final_stack_pwd = urlutils.join(
309
transport.base, final_stack_pwd.decode('utf-8'))
310
remote_repo = RemoteRepository(repo_bzr, repo_format)
311
if len(response) > 10:
312
# Updated server verb that locks remotely.
313
repo_lock_token = response[10] or None
314
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
316
remote_repo.dont_leave_lock_in_place()
318
remote_repo.lock_write()
319
policy = _mod_bzrdir.UseExistingRepository(remote_repo,
320
final_stack, final_stack_pwd, require_stacking)
321
policy.acquire_repository()
325
bzrdir._format.set_branch_format(self.get_branch_format())
327
# The repo has already been created, but we need to make sure that
328
# we'll make a stackable branch.
329
bzrdir._format.require_stacking(_skip_repo=True)
330
return remote_repo, bzrdir, require_stacking, policy
332
def _open(self, transport):
333
return RemoteBzrDir(transport, self)
335
def __eq__(self, other):
336
if not isinstance(other, RemoteBzrDirFormat):
338
return self.get_format_description() == other.get_format_description()
340
def __return_repository_format(self):
341
# Always return a RemoteRepositoryFormat object, but if a specific bzr
342
# repository format has been asked for, tell the RemoteRepositoryFormat
343
# that it should use that for init() etc.
344
result = RemoteRepositoryFormat()
345
custom_format = getattr(self, '_repository_format', None)
347
if isinstance(custom_format, RemoteRepositoryFormat):
350
# We will use the custom format to create repositories over the
351
# wire; expose its details like rich_root_data for code to
353
result._custom_format = custom_format
356
def get_branch_format(self):
357
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
358
if not isinstance(result, RemoteBranchFormat):
359
new_result = RemoteBranchFormat()
360
new_result._custom_format = result
362
self.set_branch_format(new_result)
366
repository_format = property(__return_repository_format,
367
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) # .im_func)
370
class RemoteControlStore(_mod_config.IniFileStore):
371
"""Control store which attempts to use HPSS calls to retrieve control store.
373
Note that this is specific to bzr-based formats.
376
def __init__(self, bzrdir):
377
super(RemoteControlStore, self).__init__()
378
self.controldir = bzrdir
379
self._real_store = None
381
def lock_write(self, token=None):
383
return self._real_store.lock_write(token)
387
return self._real_store.unlock()
390
with self.lock_write():
391
# We need to be able to override the undecorated implementation
392
self.save_without_locking()
394
def save_without_locking(self):
395
super(RemoteControlStore, self).save()
397
def _ensure_real(self):
398
self.controldir._ensure_real()
399
if self._real_store is None:
400
self._real_store = _mod_config.ControlStore(self.controldir)
402
def external_url(self):
403
return urlutils.join(self.branch.user_url, 'control.conf')
405
def _load_content(self):
406
medium = self.controldir._client._medium
407
path = self.controldir._path_for_remote_call(self.controldir._client)
409
response, handler = self.controldir._call_expecting_body(
410
b'BzrDir.get_config_file', path)
411
except errors.UnknownSmartMethod:
413
return self._real_store._load_content()
414
if len(response) and response[0] != b'ok':
415
raise errors.UnexpectedSmartServerResponse(response)
416
return handler.read_body_bytes()
418
def _save_content(self, content):
419
# FIXME JRV 2011-11-22: Ideally this should use a
420
# HPSS call too, but at the moment it is not possible
421
# to write lock control directories.
423
return self._real_store._save_content(content)
426
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
427
"""Control directory on a remote server, accessed via bzr:// or similar."""
429
def __init__(self, transport, format, _client=None, _force_probe=False):
430
"""Construct a RemoteBzrDir.
432
:param _client: Private parameter for testing. Disables probing and the
433
use of a real bzrdir.
435
_mod_bzrdir.BzrDir.__init__(self, transport, format)
436
# this object holds a delegated bzrdir that uses file-level operations
437
# to talk to the other side
438
self._real_bzrdir = None
439
self._has_working_tree = None
440
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
441
# create_branch for details.
442
self._next_open_branch_result = None
445
medium = transport.get_smart_medium()
446
self._client = client._SmartClient(medium)
448
self._client = _client
455
return '%s(%r)' % (self.__class__.__name__, self._client)
457
def _probe_bzrdir(self):
458
medium = self._client._medium
459
path = self._path_for_remote_call(self._client)
460
if medium._is_remote_before((2, 1)):
464
self._rpc_open_2_1(path)
466
except errors.UnknownSmartMethod:
467
medium._remember_remote_is_before((2, 1))
470
def _rpc_open_2_1(self, path):
471
response = self._call(b'BzrDir.open_2.1', path)
472
if response == (b'no',):
473
raise errors.NotBranchError(path=self.root_transport.base)
474
elif response[0] == b'yes':
475
if response[1] == b'yes':
476
self._has_working_tree = True
477
elif response[1] == b'no':
478
self._has_working_tree = False
480
raise errors.UnexpectedSmartServerResponse(response)
482
raise errors.UnexpectedSmartServerResponse(response)
484
def _rpc_open(self, path):
485
response = self._call(b'BzrDir.open', path)
486
if response not in [(b'yes',), (b'no',)]:
487
raise errors.UnexpectedSmartServerResponse(response)
488
if response == (b'no',):
489
raise errors.NotBranchError(path=self.root_transport.base)
491
def _ensure_real(self):
492
"""Ensure that there is a _real_bzrdir set.
494
Used before calls to self._real_bzrdir.
496
if not self._real_bzrdir:
497
if 'hpssvfs' in debug.debug_flags:
499
warning('VFS BzrDir access triggered\n%s',
500
''.join(traceback.format_stack()))
501
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
502
self.root_transport, probers=[_mod_bzr.BzrProber])
503
self._format._network_name = \
504
self._real_bzrdir._format.network_name()
506
def _translate_error(self, err, **context):
507
_translate_error(err, bzrdir=self, **context)
509
def break_lock(self):
510
# Prevent aliasing problems in the next_open_branch_result cache.
511
# See create_branch for rationale.
512
self._next_open_branch_result = None
513
return _mod_bzrdir.BzrDir.break_lock(self)
515
def _vfs_checkout_metadir(self):
517
return self._real_bzrdir.checkout_metadir()
519
def checkout_metadir(self):
520
"""Retrieve the controldir format to use for checkouts of this one.
522
medium = self._client._medium
523
if medium._is_remote_before((2, 5)):
524
return self._vfs_checkout_metadir()
525
path = self._path_for_remote_call(self._client)
527
response = self._client.call(b'BzrDir.checkout_metadir',
529
except errors.UnknownSmartMethod:
530
medium._remember_remote_is_before((2, 5))
531
return self._vfs_checkout_metadir()
532
if len(response) != 3:
533
raise errors.UnexpectedSmartServerResponse(response)
534
control_name, repo_name, branch_name = response
536
format = controldir.network_format_registry.get(control_name)
538
raise errors.UnknownFormatError(kind='control',
542
repo_format = _mod_repository.network_format_registry.get(
545
raise errors.UnknownFormatError(kind='repository',
547
format.repository_format = repo_format
550
format.set_branch_format(
551
branch.network_format_registry.get(branch_name))
553
raise errors.UnknownFormatError(kind='branch',
557
def _vfs_cloning_metadir(self, require_stacking=False):
559
return self._real_bzrdir.cloning_metadir(
560
require_stacking=require_stacking)
562
def cloning_metadir(self, require_stacking=False):
563
medium = self._client._medium
564
if medium._is_remote_before((1, 13)):
565
return self._vfs_cloning_metadir(require_stacking=require_stacking)
566
verb = b'BzrDir.cloning_metadir'
571
path = self._path_for_remote_call(self._client)
573
response = self._call(verb, path, stacking)
574
except errors.UnknownSmartMethod:
575
medium._remember_remote_is_before((1, 13))
576
return self._vfs_cloning_metadir(require_stacking=require_stacking)
577
except errors.UnknownErrorFromSmartServer as err:
578
if err.error_tuple != (b'BranchReference',):
580
# We need to resolve the branch reference to determine the
581
# cloning_metadir. This causes unnecessary RPCs to open the
582
# referenced branch (and bzrdir, etc) but only when the caller
583
# didn't already resolve the branch reference.
584
referenced_branch = self.open_branch()
585
return referenced_branch.controldir.cloning_metadir()
586
if len(response) != 3:
587
raise errors.UnexpectedSmartServerResponse(response)
588
control_name, repo_name, branch_info = response
589
if len(branch_info) != 2:
590
raise errors.UnexpectedSmartServerResponse(response)
591
branch_ref, branch_name = branch_info
593
format = controldir.network_format_registry.get(control_name)
595
raise errors.UnknownFormatError(
596
kind='control', format=control_name)
600
format.repository_format = _mod_repository.network_format_registry.get(
603
raise errors.UnknownFormatError(kind='repository',
605
if branch_ref == b'ref':
606
# XXX: we need possible_transports here to avoid reopening the
607
# connection to the referenced location
608
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
609
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
610
format.set_branch_format(branch_format)
611
elif branch_ref == b'branch':
614
branch_format = branch.network_format_registry.get(
617
raise errors.UnknownFormatError(kind='branch',
619
format.set_branch_format(branch_format)
621
raise errors.UnexpectedSmartServerResponse(response)
624
def create_repository(self, shared=False):
625
# as per meta1 formats - just delegate to the format object which may
627
result = self._format.repository_format.initialize(self, shared)
628
if not isinstance(result, RemoteRepository):
629
return self.open_repository()
633
def destroy_repository(self):
634
"""See BzrDir.destroy_repository"""
635
path = self._path_for_remote_call(self._client)
637
response = self._call(b'BzrDir.destroy_repository', path)
638
except errors.UnknownSmartMethod:
640
self._real_bzrdir.destroy_repository()
642
if response[0] != b'ok':
643
raise SmartProtocolError(
644
'unexpected response code %s' % (response,))
646
def create_branch(self, name=None, repository=None,
647
append_revisions_only=None):
649
name = self._get_selected_branch()
651
raise errors.NoColocatedBranchSupport(self)
652
# as per meta1 formats - just delegate to the format object which may
654
real_branch = self._format.get_branch_format().initialize(self,
655
name=name, repository=repository,
656
append_revisions_only=append_revisions_only)
657
if not isinstance(real_branch, RemoteBranch):
658
if not isinstance(repository, RemoteRepository):
659
raise AssertionError(
660
'need a RemoteRepository to use with RemoteBranch, got %r'
662
result = RemoteBranch(self, repository, real_branch, name=name)
665
# BzrDir.clone_on_transport() uses the result of create_branch but does
666
# not return it to its callers; we save approximately 8% of our round
667
# trips by handing the branch we created back to the first caller to
668
# open_branch rather than probing anew. Long term we need a API in
669
# bzrdir that doesn't discard result objects (like result_branch).
671
self._next_open_branch_result = result
674
def destroy_branch(self, name=None):
675
"""See BzrDir.destroy_branch"""
677
name = self._get_selected_branch()
679
raise errors.NoColocatedBranchSupport(self)
680
path = self._path_for_remote_call(self._client)
686
response = self._call(b'BzrDir.destroy_branch', path, *args)
687
except errors.UnknownSmartMethod:
689
self._real_bzrdir.destroy_branch(name=name)
690
self._next_open_branch_result = None
692
self._next_open_branch_result = None
693
if response[0] != b'ok':
694
raise SmartProtocolError(
695
'unexpected response code %s' % (response,))
697
def create_workingtree(self, revision_id=None, from_branch=None,
698
accelerator_tree=None, hardlink=False):
699
raise errors.NotLocalUrl(self.transport.base)
701
def find_branch_format(self, name=None):
702
"""Find the branch 'format' for this bzrdir.
704
This might be a synthetic object for e.g. RemoteBranch and SVN.
706
b = self.open_branch(name=name)
709
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
710
path = self._path_for_remote_call(self._client)
712
response, handler = self._call_expecting_body(
713
b'BzrDir.get_branches', path)
714
except errors.UnknownSmartMethod:
716
return self._real_bzrdir.get_branches()
717
if response[0] != b"success":
718
raise errors.UnexpectedSmartServerResponse(response)
719
body = bencode.bdecode(handler.read_body_bytes())
721
for name, value in viewitems(body):
722
name = name.decode('utf-8')
723
ret[name] = self._open_branch(name, value[0], value[1],
724
possible_transports=possible_transports,
725
ignore_fallbacks=ignore_fallbacks)
728
def set_branch_reference(self, target_branch, name=None):
729
"""See BzrDir.set_branch_reference()."""
731
name = self._get_selected_branch()
733
raise errors.NoColocatedBranchSupport(self)
735
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
737
def get_branch_reference(self, name=None):
738
"""See BzrDir.get_branch_reference()."""
740
name = self._get_selected_branch()
742
raise errors.NoColocatedBranchSupport(self)
743
response = self._get_branch_reference()
744
if response[0] == 'ref':
745
return response[1].decode('utf-8')
749
def _get_branch_reference(self):
750
"""Get branch reference information
752
:return: Tuple with (kind, location_or_format)
753
if kind == 'ref', then location_or_format contains a location
754
otherwise, it contains a format name
756
path = self._path_for_remote_call(self._client)
757
medium = self._client._medium
759
(b'BzrDir.open_branchV3', (2, 1)),
760
(b'BzrDir.open_branchV2', (1, 13)),
761
(b'BzrDir.open_branch', None),
763
for verb, required_version in candidate_calls:
764
if required_version and medium._is_remote_before(required_version):
767
response = self._call(verb, path)
768
except errors.UnknownSmartMethod:
769
if required_version is None:
771
medium._remember_remote_is_before(required_version)
774
if verb == b'BzrDir.open_branch':
775
if response[0] != b'ok':
776
raise errors.UnexpectedSmartServerResponse(response)
777
if response[1] != b'':
778
return ('ref', response[1])
780
return ('branch', b'')
781
if response[0] not in (b'ref', b'branch'):
782
raise errors.UnexpectedSmartServerResponse(response)
783
return (response[0].decode('ascii'), response[1])
785
def _get_tree_branch(self, name=None):
786
"""See BzrDir._get_tree_branch()."""
787
return None, self.open_branch(name=name)
789
def _open_branch(self, name, kind, location_or_format,
790
ignore_fallbacks=False, possible_transports=None):
792
# a branch reference, use the existing BranchReference logic.
793
format = BranchReferenceFormat()
794
return format.open(self, name=name, _found=True,
795
location=location_or_format.decode('utf-8'),
796
ignore_fallbacks=ignore_fallbacks,
797
possible_transports=possible_transports)
798
branch_format_name = location_or_format
799
if not branch_format_name:
800
branch_format_name = None
801
format = RemoteBranchFormat(network_name=branch_format_name)
802
return RemoteBranch(self, self.find_repository(), format=format,
803
setup_stacking=not ignore_fallbacks, name=name,
804
possible_transports=possible_transports)
806
def open_branch(self, name=None, unsupported=False,
807
ignore_fallbacks=False, possible_transports=None):
809
name = self._get_selected_branch()
811
raise errors.NoColocatedBranchSupport(self)
813
raise NotImplementedError(
814
'unsupported flag support not implemented yet.')
815
if self._next_open_branch_result is not None:
816
# See create_branch for details.
817
result = self._next_open_branch_result
818
self._next_open_branch_result = None
820
response = self._get_branch_reference()
821
return self._open_branch(name, response[0], response[1],
822
possible_transports=possible_transports,
823
ignore_fallbacks=ignore_fallbacks)
825
def _open_repo_v1(self, path):
826
verb = b'BzrDir.find_repository'
827
response = self._call(verb, path)
828
if response[0] != b'ok':
829
raise errors.UnexpectedSmartServerResponse(response)
830
# servers that only support the v1 method don't support external
833
repo = self._real_bzrdir.open_repository()
834
response = response + (b'no', repo._format.network_name())
835
return response, repo
837
def _open_repo_v2(self, path):
838
verb = b'BzrDir.find_repositoryV2'
839
response = self._call(verb, path)
840
if response[0] != b'ok':
841
raise errors.UnexpectedSmartServerResponse(response)
843
repo = self._real_bzrdir.open_repository()
844
response = response + (repo._format.network_name(),)
845
return response, repo
847
def _open_repo_v3(self, path):
848
verb = b'BzrDir.find_repositoryV3'
849
medium = self._client._medium
850
if medium._is_remote_before((1, 13)):
851
raise errors.UnknownSmartMethod(verb)
853
response = self._call(verb, path)
854
except errors.UnknownSmartMethod:
855
medium._remember_remote_is_before((1, 13))
857
if response[0] != b'ok':
858
raise errors.UnexpectedSmartServerResponse(response)
859
return response, None
861
def open_repository(self):
862
path = self._path_for_remote_call(self._client)
864
for probe in [self._open_repo_v3, self._open_repo_v2,
867
response, real_repo = probe(path)
869
except errors.UnknownSmartMethod:
872
raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
873
if response[0] != b'ok':
874
raise errors.UnexpectedSmartServerResponse(response)
875
if len(response) != 6:
876
raise SmartProtocolError(
877
'incorrect response length %s' % (response,))
878
if response[1] == b'':
879
# repo is at this dir.
880
format = response_tuple_to_repo_format(response[2:])
881
# Used to support creating a real format instance when needed.
882
format._creating_bzrdir = self
883
remote_repo = RemoteRepository(self, format)
884
format._creating_repo = remote_repo
885
if real_repo is not None:
886
remote_repo._set_real_repository(real_repo)
889
raise errors.NoRepositoryPresent(self)
891
def has_workingtree(self):
892
if self._has_working_tree is None:
893
path = self._path_for_remote_call(self._client)
895
response = self._call(b'BzrDir.has_workingtree', path)
896
except errors.UnknownSmartMethod:
898
self._has_working_tree = self._real_bzrdir.has_workingtree()
900
if response[0] not in (b'yes', b'no'):
901
raise SmartProtocolError(
902
'unexpected response code %s' % (response,))
903
self._has_working_tree = (response[0] == b'yes')
904
return self._has_working_tree
906
def open_workingtree(self, recommend_upgrade=True):
907
if self.has_workingtree():
908
raise errors.NotLocalUrl(self.root_transport)
910
raise errors.NoWorkingTree(self.root_transport.base)
912
def _path_for_remote_call(self, client):
913
"""Return the path to be used for this bzrdir in a remote call."""
914
remote_path = client.remote_path_from_transport(self.root_transport)
915
if sys.version_info[0] == 3:
916
remote_path = remote_path.decode('utf-8')
917
base_url, segment_parameters = urlutils.split_segment_parameters_raw(
919
if sys.version_info[0] == 3:
920
base_url = base_url.encode('utf-8')
923
def get_branch_transport(self, branch_format, name=None):
925
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
927
def get_repository_transport(self, repository_format):
929
return self._real_bzrdir.get_repository_transport(repository_format)
931
def get_workingtree_transport(self, workingtree_format):
933
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
935
def can_convert_format(self):
936
"""Upgrading of remote bzrdirs is not supported yet."""
939
def needs_format_conversion(self, format):
940
"""Upgrading of remote bzrdirs is not supported yet."""
943
def _get_config(self):
944
return RemoteBzrDirConfig(self)
946
def _get_config_store(self):
947
return RemoteControlStore(self)
950
class RemoteInventoryTree(InventoryRevisionTree):
952
def __init__(self, repository, inv, revision_id):
953
super(RemoteInventoryTree, self).__init__(repository, inv, revision_id)
955
def archive(self, format, name, root=None, subdir=None, force_mtime=None):
956
ret = self._repository._revision_archive(
957
self.get_revision_id(), format, name, root, subdir,
958
force_mtime=force_mtime)
960
return super(RemoteInventoryTree, self).archive(
961
format, name, root, subdir, force_mtime=force_mtime)
964
def annotate_iter(self, path, file_id=None,
965
default_revision=_mod_revision.CURRENT_REVISION):
966
"""Return an iterator of revision_id, line tuples.
968
For working trees (and mutable trees in general), the special
969
revision_id 'current:' will be used for lines that are new in this
970
tree, e.g. uncommitted changes.
971
:param file_id: The file to produce an annotated version from
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, default_revision)
979
return super(RemoteInventoryTree, self).annotate_iter(
980
path, file_id, default_revision=default_revision)
984
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
985
"""Format for repositories accessed over a _SmartClient.
987
Instances of this repository are represented by RemoteRepository
990
The RemoteRepositoryFormat is parameterized during construction
991
to reflect the capabilities of the real, remote format. Specifically
992
the attributes rich_root_data and supports_tree_reference are set
993
on a per instance basis, and are not set (and should not be) at
996
:ivar _custom_format: If set, a specific concrete repository format that
997
will be used when initializing a repository with this
998
RemoteRepositoryFormat.
999
:ivar _creating_repo: If set, the repository object that this
1000
RemoteRepositoryFormat was created for: it can be called into
1001
to obtain data like the network name.
1004
_matchingcontroldir = RemoteBzrDirFormat()
1005
supports_full_versioned_files = True
1006
supports_leaving_lock = True
1007
supports_overriding_transport = False
1010
_mod_repository.RepositoryFormat.__init__(self)
1011
self._custom_format = None
1012
self._network_name = None
1013
self._creating_bzrdir = None
1014
self._revision_graph_can_have_wrong_parents = None
1015
self._supports_chks = None
1016
self._supports_external_lookups = None
1017
self._supports_tree_reference = None
1018
self._supports_funky_characters = None
1019
self._supports_nesting_repositories = None
1020
self._rich_root_data = None
1023
return "%s(_network_name=%r)" % (self.__class__.__name__,
1027
def fast_deltas(self):
1029
return self._custom_format.fast_deltas
1032
def rich_root_data(self):
1033
if self._rich_root_data is None:
1035
self._rich_root_data = self._custom_format.rich_root_data
1036
return self._rich_root_data
1039
def supports_chks(self):
1040
if self._supports_chks is None:
1042
self._supports_chks = self._custom_format.supports_chks
1043
return self._supports_chks
1046
def supports_external_lookups(self):
1047
if self._supports_external_lookups is None:
1049
self._supports_external_lookups = \
1050
self._custom_format.supports_external_lookups
1051
return self._supports_external_lookups
1054
def supports_funky_characters(self):
1055
if self._supports_funky_characters is None:
1057
self._supports_funky_characters = \
1058
self._custom_format.supports_funky_characters
1059
return self._supports_funky_characters
1062
def supports_nesting_repositories(self):
1063
if self._supports_nesting_repositories is None:
1065
self._supports_nesting_repositories = \
1066
self._custom_format.supports_nesting_repositories
1067
return self._supports_nesting_repositories
1070
def supports_tree_reference(self):
1071
if self._supports_tree_reference is None:
1073
self._supports_tree_reference = \
1074
self._custom_format.supports_tree_reference
1075
return self._supports_tree_reference
1078
def revision_graph_can_have_wrong_parents(self):
1079
if self._revision_graph_can_have_wrong_parents is None:
1081
self._revision_graph_can_have_wrong_parents = \
1082
self._custom_format.revision_graph_can_have_wrong_parents
1083
return self._revision_graph_can_have_wrong_parents
1085
def _vfs_initialize(self, a_controldir, shared):
1086
"""Helper for common code in initialize."""
1087
if self._custom_format:
1088
# Custom format requested
1089
result = self._custom_format.initialize(
1090
a_controldir, shared=shared)
1091
elif self._creating_bzrdir is not None:
1092
# Use the format that the repository we were created to back
1094
prior_repo = self._creating_bzrdir.open_repository()
1095
prior_repo._ensure_real()
1096
result = prior_repo._real_repository._format.initialize(
1097
a_controldir, shared=shared)
1099
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1100
# support remote initialization.
1101
# We delegate to a real object at this point (as RemoteBzrDir
1102
# delegate to the repository format which would lead to infinite
1103
# recursion if we just called a_controldir.create_repository.
1104
a_controldir._ensure_real()
1105
result = a_controldir._real_bzrdir.create_repository(shared=shared)
1106
if not isinstance(result, RemoteRepository):
1107
return self.open(a_controldir)
1111
def initialize(self, a_controldir, shared=False):
1112
# Being asked to create on a non RemoteBzrDir:
1113
if not isinstance(a_controldir, RemoteBzrDir):
1114
return self._vfs_initialize(a_controldir, shared)
1115
medium = a_controldir._client._medium
1116
if medium._is_remote_before((1, 13)):
1117
return self._vfs_initialize(a_controldir, shared)
1118
# Creating on a remote bzr dir.
1119
# 1) get the network name to use.
1120
if self._custom_format:
1121
network_name = self._custom_format.network_name()
1122
elif self._network_name:
1123
network_name = self._network_name
1125
# Select the current breezy default and ask for that.
1126
reference_bzrdir_format = controldir.format_registry.get(
1128
reference_format = reference_bzrdir_format.repository_format
1129
network_name = reference_format.network_name()
1130
# 2) try direct creation via RPC
1131
path = a_controldir._path_for_remote_call(a_controldir._client)
1132
verb = b'BzrDir.create_repository'
1134
shared_str = b'True'
1136
shared_str = b'False'
1138
response = a_controldir._call(verb, path, network_name, shared_str)
1139
except errors.UnknownSmartMethod:
1140
# Fallback - use vfs methods
1141
medium._remember_remote_is_before((1, 13))
1142
return self._vfs_initialize(a_controldir, shared)
1144
# Turn the response into a RemoteRepository object.
1145
format = response_tuple_to_repo_format(response[1:])
1146
# Used to support creating a real format instance when needed.
1147
format._creating_bzrdir = a_controldir
1148
remote_repo = RemoteRepository(a_controldir, format)
1149
format._creating_repo = remote_repo
1152
def open(self, a_controldir):
1153
if not isinstance(a_controldir, RemoteBzrDir):
1154
raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
1155
return a_controldir.open_repository()
1157
def _ensure_real(self):
1158
if self._custom_format is None:
1160
self._custom_format = _mod_repository.network_format_registry.get(
1163
raise errors.UnknownFormatError(kind='repository',
1164
format=self._network_name)
1167
def _fetch_order(self):
1169
return self._custom_format._fetch_order
1172
def _fetch_uses_deltas(self):
1174
return self._custom_format._fetch_uses_deltas
1177
def _fetch_reconcile(self):
1179
return self._custom_format._fetch_reconcile
1181
def get_format_description(self):
1183
return 'Remote: ' + self._custom_format.get_format_description()
1185
def __eq__(self, other):
1186
return self.__class__ is other.__class__
1188
def network_name(self):
1189
if self._network_name:
1190
return self._network_name
1191
self._creating_repo._ensure_real()
1192
return self._creating_repo._real_repository._format.network_name()
1195
def pack_compresses(self):
1197
return self._custom_format.pack_compresses
1200
def _serializer(self):
1202
return self._custom_format._serializer
1205
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1206
lock._RelockDebugMixin):
1207
"""Repository accessed over rpc.
1209
For the moment most operations are performed using local transport-backed
1213
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1214
"""Create a RemoteRepository instance.
1216
:param remote_bzrdir: The bzrdir hosting this repository.
1217
:param format: The RemoteFormat object to use.
1218
:param real_repository: If not None, a local implementation of the
1219
repository logic for the repository, usually accessing the data
1221
:param _client: Private testing parameter - override the smart client
1222
to be used by the repository.
1225
self._real_repository = real_repository
1227
self._real_repository = None
1228
self.controldir = remote_bzrdir
1230
self._client = remote_bzrdir._client
1232
self._client = _client
1233
self._format = format
1234
self._lock_mode = None
1235
self._lock_token = None
1236
self._write_group_tokens = None
1237
self._lock_count = 0
1238
self._leave_lock = False
1239
# Cache of revision parents; misses are cached during read locks, and
1240
# write locks when no _real_repository has been set.
1241
self._unstacked_provider = graph.CachingParentsProvider(
1242
get_parent_map=self._get_parent_map_rpc)
1243
self._unstacked_provider.disable_cache()
1245
# These depend on the actual remote format, so force them off for
1246
# maximum compatibility. XXX: In future these should depend on the
1247
# remote repository instance, but this is irrelevant until we perform
1248
# reconcile via an RPC call.
1249
self._reconcile_does_inventory_gc = False
1250
self._reconcile_fixes_text_parents = False
1251
self._reconcile_backsup_inventory = False
1252
self.base = self.controldir.transport.base
1253
# Additional places to query for data.
1254
self._fallback_repositories = []
1257
def user_transport(self):
1258
return self.controldir.user_transport
1261
def control_transport(self):
1262
# XXX: Normally you shouldn't directly get at the remote repository
1263
# transport, but I'm not sure it's worth making this method
1264
# optional -- mbp 2010-04-21
1265
return self.controldir.get_repository_transport(None)
1268
return "%s(%s)" % (self.__class__.__name__, self.base)
1272
def abort_write_group(self, suppress_errors=False):
1273
"""Complete a write group on the decorated repository.
1275
Smart methods perform operations in a single step so this API
1276
is not really applicable except as a compatibility thunk
1277
for older plugins that don't use e.g. the CommitBuilder
1280
:param suppress_errors: see Repository.abort_write_group.
1282
if self._real_repository:
1284
return self._real_repository.abort_write_group(
1285
suppress_errors=suppress_errors)
1286
if not self.is_in_write_group():
1288
mutter('(suppressed) not in write group')
1290
raise errors.BzrError("not in write group")
1291
path = self.controldir._path_for_remote_call(self._client)
1293
response = self._call(b'Repository.abort_write_group', path,
1295
[token.encode('utf-8') for token in self._write_group_tokens])
1296
except Exception as exc:
1297
self._write_group = None
1298
if not suppress_errors:
1300
mutter('abort_write_group failed')
1301
log_exception_quietly()
1302
note(gettext('bzr: ERROR (ignored): %s'), exc)
1304
if response != (b'ok', ):
1305
raise errors.UnexpectedSmartServerResponse(response)
1306
self._write_group_tokens = None
1309
def chk_bytes(self):
1310
"""Decorate the real repository for now.
1312
In the long term a full blown network facility is needed to avoid
1313
creating a real repository object locally.
1316
return self._real_repository.chk_bytes
1318
def commit_write_group(self):
1319
"""Complete a write group on the decorated repository.
1321
Smart methods perform operations in a single step so this API
1322
is not really applicable except as a compatibility thunk
1323
for older plugins that don't use e.g. the CommitBuilder
1326
if self._real_repository:
1328
return self._real_repository.commit_write_group()
1329
if not self.is_in_write_group():
1330
raise errors.BzrError("not in write group")
1331
path = self.controldir._path_for_remote_call(self._client)
1332
response = self._call(b'Repository.commit_write_group', path,
1333
self._lock_token, [token.encode('utf-8') for token in self._write_group_tokens])
1334
if response != (b'ok', ):
1335
raise errors.UnexpectedSmartServerResponse(response)
1336
self._write_group_tokens = None
1337
# Refresh data after writing to the repository.
1340
def resume_write_group(self, tokens):
1341
if self._real_repository:
1342
return self._real_repository.resume_write_group(tokens)
1343
path = self.controldir._path_for_remote_call(self._client)
1345
response = self._call(b'Repository.check_write_group', path,
1346
self._lock_token, [token.encode('utf-8') for token in tokens])
1347
except errors.UnknownSmartMethod:
1349
return self._real_repository.resume_write_group(tokens)
1350
if response != (b'ok', ):
1351
raise errors.UnexpectedSmartServerResponse(response)
1352
self._write_group_tokens = tokens
1354
def suspend_write_group(self):
1355
if self._real_repository:
1356
return self._real_repository.suspend_write_group()
1357
ret = self._write_group_tokens or []
1358
self._write_group_tokens = None
1361
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1363
return self._real_repository.get_missing_parent_inventories(
1364
check_for_missing_texts=check_for_missing_texts)
1366
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1368
return self._real_repository.get_rev_id_for_revno(
1371
def get_rev_id_for_revno(self, revno, known_pair):
1372
"""See Repository.get_rev_id_for_revno."""
1373
path = self.controldir._path_for_remote_call(self._client)
1375
if self._client._medium._is_remote_before((1, 17)):
1376
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1377
response = self._call(
1378
b'Repository.get_rev_id_for_revno', path, revno, known_pair)
1379
except errors.UnknownSmartMethod:
1380
self._client._medium._remember_remote_is_before((1, 17))
1381
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1382
if response[0] == b'ok':
1383
return True, response[1]
1384
elif response[0] == b'history-incomplete':
1385
known_pair = response[1:3]
1386
for fallback in self._fallback_repositories:
1387
found, result = fallback.get_rev_id_for_revno(
1393
# Not found in any fallbacks
1394
return False, known_pair
1396
raise errors.UnexpectedSmartServerResponse(response)
1398
def _ensure_real(self):
1399
"""Ensure that there is a _real_repository set.
1401
Used before calls to self._real_repository.
1403
Note that _ensure_real causes many roundtrips to the server which are
1404
not desirable, and prevents the use of smart one-roundtrip RPC's to
1405
perform complex operations (such as accessing parent data, streaming
1406
revisions etc). Adding calls to _ensure_real should only be done when
1407
bringing up new functionality, adding fallbacks for smart methods that
1408
require a fallback path, and never to replace an existing smart method
1409
invocation. If in doubt chat to the bzr network team.
1411
if self._real_repository is None:
1412
if 'hpssvfs' in debug.debug_flags:
1414
warning('VFS Repository access triggered\n%s',
1415
''.join(traceback.format_stack()))
1416
self._unstacked_provider.missing_keys.clear()
1417
self.controldir._ensure_real()
1418
self._set_real_repository(
1419
self.controldir._real_bzrdir.open_repository())
1421
def _translate_error(self, err, **context):
1422
self.controldir._translate_error(err, repository=self, **context)
1424
def find_text_key_references(self):
1425
"""Find the text key references within the repository.
1427
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1428
to whether they were referred to by the inventory of the
1429
revision_id that they contain. The inventory texts from all present
1430
revision ids are assessed to generate this report.
1433
return self._real_repository.find_text_key_references()
1435
def _generate_text_key_index(self):
1436
"""Generate a new text key index for the repository.
1438
This is an expensive function that will take considerable time to run.
1440
:return: A dict mapping (file_id, revision_id) tuples to a list of
1441
parents, also (file_id, revision_id) tuples.
1444
return self._real_repository._generate_text_key_index()
1446
def _get_revision_graph(self, revision_id):
1447
"""Private method for using with old (< 1.2) servers to fallback."""
1448
if revision_id is None:
1450
elif _mod_revision.is_null(revision_id):
1453
path = self.controldir._path_for_remote_call(self._client)
1454
response = self._call_expecting_body(
1455
b'Repository.get_revision_graph', path, revision_id)
1456
response_tuple, response_handler = response
1457
if response_tuple[0] != b'ok':
1458
raise errors.UnexpectedSmartServerResponse(response_tuple)
1459
coded = response_handler.read_body_bytes()
1461
# no revisions in this repository!
1463
lines = coded.split(b'\n')
1466
d = tuple(line.split())
1467
revision_graph[d[0]] = d[1:]
1469
return revision_graph
1471
def _get_sink(self):
1472
"""See Repository._get_sink()."""
1473
return RemoteStreamSink(self)
1475
def _get_source(self, to_format):
1476
"""Return a source for streaming from this repository."""
1477
return RemoteStreamSource(self, to_format)
1479
def get_file_graph(self):
1480
with self.lock_read():
1481
return graph.Graph(self.texts)
1483
def has_revision(self, revision_id):
1484
"""True if this repository has a copy of the revision."""
1485
# Copy of breezy.repository.Repository.has_revision
1486
with self.lock_read():
1487
return revision_id in self.has_revisions((revision_id,))
1489
def has_revisions(self, revision_ids):
1490
"""Probe to find out the presence of multiple revisions.
1492
:param revision_ids: An iterable of revision_ids.
1493
:return: A set of the revision_ids that were present.
1495
with self.lock_read():
1496
# Copy of breezy.repository.Repository.has_revisions
1497
parent_map = self.get_parent_map(revision_ids)
1498
result = set(parent_map)
1499
if _mod_revision.NULL_REVISION in revision_ids:
1500
result.add(_mod_revision.NULL_REVISION)
1503
def _has_same_fallbacks(self, other_repo):
1504
"""Returns true if the repositories have the same fallbacks."""
1505
# XXX: copied from Repository; it should be unified into a base class
1506
# <https://bugs.launchpad.net/bzr/+bug/401622>
1507
my_fb = self._fallback_repositories
1508
other_fb = other_repo._fallback_repositories
1509
if len(my_fb) != len(other_fb):
1511
for f, g in zip(my_fb, other_fb):
1512
if not f.has_same_location(g):
1516
def has_same_location(self, other):
1517
# TODO: Move to RepositoryBase and unify with the regular Repository
1518
# one; unfortunately the tests rely on slightly different behaviour at
1519
# present -- mbp 20090710
1520
return (self.__class__ is other.__class__
1521
and self.controldir.transport.base == other.controldir.transport.base)
1523
def get_graph(self, other_repository=None):
1524
"""Return the graph for this repository format"""
1525
parents_provider = self._make_parents_provider(other_repository)
1526
return graph.Graph(parents_provider)
1528
def get_known_graph_ancestry(self, revision_ids):
1529
"""Return the known graph for a set of revision ids and their ancestors.
1531
with self.lock_read():
1532
revision_graph = dict(((key, value) for key, value in
1533
self.get_graph().iter_ancestry(revision_ids) if value is not None))
1534
revision_graph = _mod_repository._strip_NULL_ghosts(revision_graph)
1535
return graph.KnownGraph(revision_graph)
1537
def gather_stats(self, revid=None, committers=None):
1538
"""See Repository.gather_stats()."""
1539
path = self.controldir._path_for_remote_call(self._client)
1540
# revid can be None to indicate no revisions, not just NULL_REVISION
1541
if revid is None or _mod_revision.is_null(revid):
1545
if committers is None or not committers:
1546
fmt_committers = b'no'
1548
fmt_committers = b'yes'
1549
response_tuple, response_handler = self._call_expecting_body(
1550
b'Repository.gather_stats', path, fmt_revid, fmt_committers)
1551
if response_tuple[0] != b'ok':
1552
raise errors.UnexpectedSmartServerResponse(response_tuple)
1554
body = response_handler.read_body_bytes()
1556
for line in body.split(b'\n'):
1559
key, val_text = line.split(b':')
1560
key = key.decode('ascii')
1561
if key in ('revisions', 'size', 'committers'):
1562
result[key] = int(val_text)
1563
elif key in ('firstrev', 'latestrev'):
1564
values = val_text.split(b' ')[1:]
1565
result[key] = (float(values[0]), int(values[1]))
1569
def find_branches(self, using=False):
1570
"""See Repository.find_branches()."""
1571
# should be an API call to the server.
1573
return self._real_repository.find_branches(using=using)
1575
def get_physical_lock_status(self):
1576
"""See Repository.get_physical_lock_status()."""
1577
path = self.controldir._path_for_remote_call(self._client)
1579
response = self._call(b'Repository.get_physical_lock_status', path)
1580
except errors.UnknownSmartMethod:
1582
return self._real_repository.get_physical_lock_status()
1583
if response[0] not in (b'yes', b'no'):
1584
raise errors.UnexpectedSmartServerResponse(response)
1585
return (response[0] == b'yes')
1587
def is_in_write_group(self):
1588
"""Return True if there is an open write group.
1590
write groups are only applicable locally for the smart server..
1592
if self._write_group_tokens is not None:
1594
if self._real_repository:
1595
return self._real_repository.is_in_write_group()
1597
def is_locked(self):
1598
return self._lock_count >= 1
1600
def is_shared(self):
1601
"""See Repository.is_shared()."""
1602
path = self.controldir._path_for_remote_call(self._client)
1603
response = self._call(b'Repository.is_shared', path)
1604
if response[0] not in (b'yes', b'no'):
1605
raise SmartProtocolError(
1606
'unexpected response code %s' % (response,))
1607
return response[0] == b'yes'
1609
def is_write_locked(self):
1610
return self._lock_mode == 'w'
1612
def _warn_if_deprecated(self, branch=None):
1613
# If we have a real repository, the check will be done there, if we
1614
# don't the check will be done remotely.
1617
def lock_read(self):
1618
"""Lock the repository for read operations.
1620
:return: A breezy.lock.LogicalLockResult.
1622
# wrong eventually - want a local lock cache context
1623
if not self._lock_mode:
1624
self._note_lock('r')
1625
self._lock_mode = 'r'
1626
self._lock_count = 1
1627
self._unstacked_provider.enable_cache(cache_misses=True)
1628
if self._real_repository is not None:
1629
self._real_repository.lock_read()
1630
for repo in self._fallback_repositories:
1633
self._lock_count += 1
1634
return lock.LogicalLockResult(self.unlock)
1636
def _remote_lock_write(self, token):
1637
path = self.controldir._path_for_remote_call(self._client)
1640
err_context = {'token': token}
1641
response = self._call(b'Repository.lock_write', path, token,
1643
if response[0] == b'ok':
1644
ok, token = response
1647
raise errors.UnexpectedSmartServerResponse(response)
1649
def lock_write(self, token=None, _skip_rpc=False):
1650
if not self._lock_mode:
1651
self._note_lock('w')
1653
if self._lock_token is not None:
1654
if token != self._lock_token:
1655
raise errors.TokenMismatch(token, self._lock_token)
1656
self._lock_token = token
1658
self._lock_token = self._remote_lock_write(token)
1659
# if self._lock_token is None, then this is something like packs or
1660
# svn where we don't get to lock the repo, or a weave style repository
1661
# where we cannot lock it over the wire and attempts to do so will
1663
if self._real_repository is not None:
1664
self._real_repository.lock_write(token=self._lock_token)
1665
if token is not None:
1666
self._leave_lock = True
1668
self._leave_lock = False
1669
self._lock_mode = 'w'
1670
self._lock_count = 1
1671
cache_misses = self._real_repository is None
1672
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1673
for repo in self._fallback_repositories:
1674
# Writes don't affect fallback repos
1676
elif self._lock_mode == 'r':
1677
raise errors.ReadOnlyError(self)
1679
self._lock_count += 1
1680
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1682
def leave_lock_in_place(self):
1683
if not self._lock_token:
1684
raise NotImplementedError(self.leave_lock_in_place)
1685
self._leave_lock = True
1687
def dont_leave_lock_in_place(self):
1688
if not self._lock_token:
1689
raise NotImplementedError(self.dont_leave_lock_in_place)
1690
self._leave_lock = False
1692
def _set_real_repository(self, repository):
1693
"""Set the _real_repository for this repository.
1695
:param repository: The repository to fallback to for non-hpss
1696
implemented operations.
1698
if self._real_repository is not None:
1699
# Replacing an already set real repository.
1700
# We cannot do this [currently] if the repository is locked -
1701
# synchronised state might be lost.
1702
if self.is_locked():
1703
raise AssertionError('_real_repository is already set')
1704
if isinstance(repository, RemoteRepository):
1705
raise AssertionError()
1706
self._real_repository = repository
1707
# three code paths happen here:
1708
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1709
# up stacking. In this case self._fallback_repositories is [], and the
1710
# real repo is already setup. Preserve the real repo and
1711
# RemoteRepository.add_fallback_repository will avoid adding
1713
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1714
# ensure_real is triggered from a branch, the real repository to
1715
# set already has a matching list with separate instances, but
1716
# as they are also RemoteRepositories we don't worry about making the
1717
# lists be identical.
1718
# 3) new servers, RemoteRepository.ensure_real is triggered before
1719
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1720
# and need to populate it.
1721
if (self._fallback_repositories
1722
and len(self._real_repository._fallback_repositories)
1723
!= len(self._fallback_repositories)):
1724
if len(self._real_repository._fallback_repositories):
1725
raise AssertionError(
1726
"cannot cleanly remove existing _fallback_repositories")
1727
for fb in self._fallback_repositories:
1728
self._real_repository.add_fallback_repository(fb)
1729
if self._lock_mode == 'w':
1730
# if we are already locked, the real repository must be able to
1731
# acquire the lock with our token.
1732
self._real_repository.lock_write(self._lock_token)
1733
elif self._lock_mode == 'r':
1734
self._real_repository.lock_read()
1735
if self._write_group_tokens is not None:
1736
# if we are already in a write group, resume it
1737
self._real_repository.resume_write_group(self._write_group_tokens)
1738
self._write_group_tokens = None
1740
def start_write_group(self):
1741
"""Start a write group on the decorated repository.
1743
Smart methods perform operations in a single step so this API
1744
is not really applicable except as a compatibility thunk
1745
for older plugins that don't use e.g. the CommitBuilder
1748
if self._real_repository:
1750
return self._real_repository.start_write_group()
1751
if not self.is_write_locked():
1752
raise errors.NotWriteLocked(self)
1753
if self._write_group_tokens is not None:
1754
raise errors.BzrError('already in a write group')
1755
path = self.controldir._path_for_remote_call(self._client)
1757
response = self._call(b'Repository.start_write_group', path,
1759
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1761
return self._real_repository.start_write_group()
1762
if response[0] != b'ok':
1763
raise errors.UnexpectedSmartServerResponse(response)
1764
self._write_group_tokens = [
1765
token.decode('utf-8') for token in response[1]]
1767
def _unlock(self, token):
1768
path = self.controldir._path_for_remote_call(self._client)
1770
# with no token the remote repository is not persistently locked.
1772
err_context = {'token': token}
1773
response = self._call(b'Repository.unlock', path, token,
1775
if response == (b'ok',):
1778
raise errors.UnexpectedSmartServerResponse(response)
1780
@only_raises(errors.LockNotHeld, errors.LockBroken)
1782
if not self._lock_count:
1783
return lock.cant_unlock_not_held(self)
1784
self._lock_count -= 1
1785
if self._lock_count > 0:
1787
self._unstacked_provider.disable_cache()
1788
old_mode = self._lock_mode
1789
self._lock_mode = None
1791
# The real repository is responsible at present for raising an
1792
# exception if it's in an unfinished write group. However, it
1793
# normally will *not* actually remove the lock from disk - that's
1794
# done by the server on receiving the Repository.unlock call.
1795
# This is just to let the _real_repository stay up to date.
1796
if self._real_repository is not None:
1797
self._real_repository.unlock()
1798
elif self._write_group_tokens is not None:
1799
self.abort_write_group()
1801
# The rpc-level lock should be released even if there was a
1802
# problem releasing the vfs-based lock.
1804
# Only write-locked repositories need to make a remote method
1805
# call to perform the unlock.
1806
old_token = self._lock_token
1807
self._lock_token = None
1808
if not self._leave_lock:
1809
self._unlock(old_token)
1810
# Fallbacks are always 'lock_read()' so we don't pay attention to
1812
for repo in self._fallback_repositories:
1815
def break_lock(self):
1816
# should hand off to the network
1817
path = self.controldir._path_for_remote_call(self._client)
1819
response = self._call(b"Repository.break_lock", path)
1820
except errors.UnknownSmartMethod:
1822
return self._real_repository.break_lock()
1823
if response != (b'ok',):
1824
raise errors.UnexpectedSmartServerResponse(response)
1826
def _get_tarball(self, compression):
1827
"""Return a TemporaryFile containing a repository tarball.
1829
Returns None if the server does not support sending tarballs.
1832
path = self.controldir._path_for_remote_call(self._client)
1834
response, protocol = self._call_expecting_body(
1835
b'Repository.tarball', path, compression.encode('ascii'))
1836
except errors.UnknownSmartMethod:
1837
protocol.cancel_read_body()
1839
if response[0] == b'ok':
1840
# Extract the tarball and return it
1841
t = tempfile.NamedTemporaryFile()
1842
# TODO: rpc layer should read directly into it...
1843
t.write(protocol.read_body_bytes())
1846
raise errors.UnexpectedSmartServerResponse(response)
1848
def sprout(self, to_bzrdir, revision_id=None):
1849
"""Create a descendent repository for new development.
1851
Unlike clone, this does not copy the settings of the repository.
1853
with self.lock_read():
1854
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1855
dest_repo.fetch(self, revision_id=revision_id)
1858
def _create_sprouting_repo(self, a_controldir, shared):
1859
if not isinstance(a_controldir._format, self.controldir._format.__class__):
1860
# use target default format.
1861
dest_repo = a_controldir.create_repository()
1863
# Most control formats need the repository to be specifically
1864
# created, but on some old all-in-one formats it's not needed
1866
dest_repo = self._format.initialize(
1867
a_controldir, shared=shared)
1868
except errors.UninitializableFormat:
1869
dest_repo = a_controldir.open_repository()
1872
# These methods are just thin shims to the VFS object for now.
1874
def revision_tree(self, revision_id):
1875
with self.lock_read():
1876
revision_id = _mod_revision.ensure_null(revision_id)
1877
if revision_id == _mod_revision.NULL_REVISION:
1878
return InventoryRevisionTree(self,
1879
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1881
return list(self.revision_trees([revision_id]))[0]
1883
def get_serializer_format(self):
1884
path = self.controldir._path_for_remote_call(self._client)
1886
response = self._call(b'VersionedFileRepository.get_serializer_format',
1888
except errors.UnknownSmartMethod:
1890
return self._real_repository.get_serializer_format()
1891
if response[0] != b'ok':
1892
raise errors.UnexpectedSmartServerResponse(response)
1895
def get_commit_builder(self, branch, parents, config, timestamp=None,
1896
timezone=None, committer=None, revprops=None,
1897
revision_id=None, lossy=False):
1898
"""Obtain a CommitBuilder for this repository.
1900
:param branch: Branch to commit to.
1901
:param parents: Revision ids of the parents of the new revision.
1902
:param config: Configuration to use.
1903
:param timestamp: Optional timestamp recorded for commit.
1904
:param timezone: Optional timezone for timestamp.
1905
:param committer: Optional committer to set for commit.
1906
:param revprops: Optional dictionary of revision properties.
1907
:param revision_id: Optional revision id.
1908
:param lossy: Whether to discard data that can not be natively
1909
represented, when pushing to a foreign VCS
1911
if self._fallback_repositories and not self._format.supports_chks:
1912
raise errors.BzrError("Cannot commit directly to a stacked branch"
1913
" in pre-2a formats. See "
1914
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1915
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1916
result = commit_builder_kls(self, parents, config,
1917
timestamp, timezone, committer, revprops, revision_id,
1919
self.start_write_group()
1922
def add_fallback_repository(self, repository):
1923
"""Add a repository to use for looking up data not held locally.
1925
:param repository: A repository.
1927
if not self._format.supports_external_lookups:
1928
raise errors.UnstackableRepositoryFormat(
1929
self._format.network_name(), self.base)
1930
# We need to accumulate additional repositories here, to pass them in
1933
# Make the check before we lock: this raises an exception.
1934
self._check_fallback_repository(repository)
1935
if self.is_locked():
1936
# We will call fallback.unlock() when we transition to the unlocked
1937
# state, so always add a lock here. If a caller passes us a locked
1938
# repository, they are responsible for unlocking it later.
1939
repository.lock_read()
1940
self._fallback_repositories.append(repository)
1941
# If self._real_repository was parameterised already (e.g. because a
1942
# _real_branch had its get_stacked_on_url method called), then the
1943
# repository to be added may already be in the _real_repositories list.
1944
if self._real_repository is not None:
1945
fallback_locations = [repo.user_url for repo in
1946
self._real_repository._fallback_repositories]
1947
if repository.user_url not in fallback_locations:
1948
self._real_repository.add_fallback_repository(repository)
1950
def _check_fallback_repository(self, repository):
1951
"""Check that this repository can fallback to repository safely.
1953
Raise an error if not.
1955
:param repository: A repository to fallback to.
1957
return _mod_repository.InterRepository._assert_same_model(
1960
def add_inventory(self, revid, inv, parents):
1962
return self._real_repository.add_inventory(revid, inv, parents)
1964
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1965
parents, basis_inv=None, propagate_caches=False):
1967
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1968
delta, new_revision_id, parents, basis_inv=basis_inv,
1969
propagate_caches=propagate_caches)
1971
def add_revision(self, revision_id, rev, inv=None):
1972
_mod_revision.check_not_reserved_id(revision_id)
1973
key = (revision_id,)
1974
# check inventory present
1975
if not self.inventories.get_parent_map([key]):
1977
raise errors.WeaveRevisionNotPresent(revision_id,
1980
# yes, this is not suitable for adding with ghosts.
1981
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1984
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1985
self._add_revision(rev)
1987
def _add_revision(self, rev):
1988
if self._real_repository is not None:
1989
return self._real_repository._add_revision(rev)
1990
text = self._serializer.write_revision_to_string(rev)
1991
key = (rev.revision_id,)
1992
parents = tuple((parent,) for parent in rev.parent_ids)
1993
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1994
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1995
self._format, self._write_group_tokens)
1997
def get_inventory(self, revision_id):
1998
with self.lock_read():
1999
return list(self.iter_inventories([revision_id]))[0]
2001
def _iter_inventories_rpc(self, revision_ids, ordering):
2002
if ordering is None:
2003
ordering = 'unordered'
2004
path = self.controldir._path_for_remote_call(self._client)
2005
body = b"\n".join(revision_ids)
2006
response_tuple, response_handler = (
2007
self._call_with_body_bytes_expecting_body(
2008
b"VersionedFileRepository.get_inventories",
2009
(path, ordering.encode('ascii')), body))
2010
if response_tuple[0] != b"ok":
2011
raise errors.UnexpectedSmartServerResponse(response_tuple)
2012
deserializer = inventory_delta.InventoryDeltaDeserializer()
2013
byte_stream = response_handler.read_streamed_body()
2014
decoded = smart_repo._byte_stream_to_stream(byte_stream)
2016
# no results whatsoever
2018
src_format, stream = decoded
2019
if src_format.network_name() != self._format.network_name():
2020
raise AssertionError(
2021
"Mismatched RemoteRepository and stream src %r, %r" % (
2022
src_format.network_name(), self._format.network_name()))
2023
# ignore the src format, it's not really relevant
2024
prev_inv = Inventory(root_id=None,
2025
revision_id=_mod_revision.NULL_REVISION)
2026
# there should be just one substream, with inventory deltas
2028
substream_kind, substream = next(stream)
2029
except StopIteration:
2031
if substream_kind != "inventory-deltas":
2032
raise AssertionError(
2033
"Unexpected stream %r received" % substream_kind)
2034
for record in substream:
2035
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
2036
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
2037
if parent_id != prev_inv.revision_id:
2038
raise AssertionError("invalid base %r != %r" % (parent_id,
2039
prev_inv.revision_id))
2040
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
2041
yield inv, inv.revision_id
2044
def _iter_inventories_vfs(self, revision_ids, ordering=None):
2046
return self._real_repository._iter_inventories(revision_ids, ordering)
2048
def iter_inventories(self, revision_ids, ordering=None):
2049
"""Get many inventories by revision_ids.
2051
This will buffer some or all of the texts used in constructing the
2052
inventories in memory, but will only parse a single inventory at a
2055
:param revision_ids: The expected revision ids of the inventories.
2056
:param ordering: optional ordering, e.g. 'topological'. If not
2057
specified, the order of revision_ids will be preserved (by
2058
buffering if necessary).
2059
:return: An iterator of inventories.
2061
if ((None in revision_ids) or
2062
(_mod_revision.NULL_REVISION in revision_ids)):
2063
raise ValueError('cannot get null revision inventory')
2064
for inv, revid in self._iter_inventories(revision_ids, ordering):
2066
raise errors.NoSuchRevision(self, revid)
2069
def _iter_inventories(self, revision_ids, ordering=None):
2070
if len(revision_ids) == 0:
2072
missing = set(revision_ids)
2073
if ordering is None:
2074
order_as_requested = True
2076
order = list(revision_ids)
2078
next_revid = order.pop()
2080
order_as_requested = False
2081
if ordering != 'unordered' and self._fallback_repositories:
2082
raise ValueError('unsupported ordering %r' % ordering)
2083
iter_inv_fns = [self._iter_inventories_rpc] + [
2084
fallback._iter_inventories for fallback in
2085
self._fallback_repositories]
2087
for iter_inv in iter_inv_fns:
2088
request = [revid for revid in revision_ids if revid in missing]
2089
for inv, revid in iter_inv(request, ordering):
2092
missing.remove(inv.revision_id)
2093
if ordering != 'unordered':
2097
if order_as_requested:
2098
# Yield as many results as we can while preserving order.
2099
while next_revid in invs:
2100
inv = invs.pop(next_revid)
2101
yield inv, inv.revision_id
2103
next_revid = order.pop()
2105
# We still want to fully consume the stream, just
2106
# in case it is not actually finished at this point
2109
except errors.UnknownSmartMethod:
2110
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2114
if order_as_requested:
2115
if next_revid is not None:
2116
yield None, next_revid
2119
yield invs.get(revid), revid
2122
yield None, missing.pop()
2124
def get_revision(self, revision_id):
2125
with self.lock_read():
2126
return self.get_revisions([revision_id])[0]
2128
def get_transaction(self):
2130
return self._real_repository.get_transaction()
2132
def clone(self, a_controldir, revision_id=None):
2133
with self.lock_read():
2134
dest_repo = self._create_sprouting_repo(
2135
a_controldir, shared=self.is_shared())
2136
self.copy_content_into(dest_repo, revision_id)
2139
def make_working_trees(self):
2140
"""See Repository.make_working_trees"""
2141
path = self.controldir._path_for_remote_call(self._client)
2143
response = self._call(b'Repository.make_working_trees', path)
2144
except errors.UnknownSmartMethod:
2146
return self._real_repository.make_working_trees()
2147
if response[0] not in (b'yes', b'no'):
2148
raise SmartProtocolError(
2149
'unexpected response code %s' % (response,))
2150
return response[0] == b'yes'
2152
def refresh_data(self):
2153
"""Re-read any data needed to synchronise with disk.
2155
This method is intended to be called after another repository instance
2156
(such as one used by a smart server) has inserted data into the
2157
repository. On all repositories this will work outside of write groups.
2158
Some repository formats (pack and newer for breezy native formats)
2159
support refresh_data inside write groups. If called inside a write
2160
group on a repository that does not support refreshing in a write group
2161
IsInWriteGroupError will be raised.
2163
if self._real_repository is not None:
2164
self._real_repository.refresh_data()
2165
# Refresh the parents cache for this object
2166
self._unstacked_provider.disable_cache()
2167
self._unstacked_provider.enable_cache()
2169
def revision_ids_to_search_result(self, result_set):
2170
"""Convert a set of revision ids to a graph SearchResult."""
2171
result_parents = set()
2172
for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
2173
result_parents.update(parents)
2174
included_keys = result_set.intersection(result_parents)
2175
start_keys = result_set.difference(included_keys)
2176
exclude_keys = result_parents.difference(result_set)
2177
result = vf_search.SearchResult(start_keys, exclude_keys,
2178
len(result_set), result_set)
2181
def search_missing_revision_ids(self, other,
2182
find_ghosts=True, revision_ids=None, if_present_ids=None,
2184
"""Return the revision ids that other has that this does not.
2186
These are returned in topological order.
2188
revision_id: only return revision ids included by revision_id.
2190
with self.lock_read():
2191
inter_repo = _mod_repository.InterRepository.get(other, self)
2192
return inter_repo.search_missing_revision_ids(
2193
find_ghosts=find_ghosts, revision_ids=revision_ids,
2194
if_present_ids=if_present_ids, limit=limit)
2196
def fetch(self, source, revision_id=None, find_ghosts=False,
2198
# No base implementation to use as RemoteRepository is not a subclass
2199
# of Repository; so this is a copy of Repository.fetch().
2200
if fetch_spec is not None and revision_id is not None:
2201
raise AssertionError(
2202
"fetch_spec and revision_id are mutually exclusive.")
2203
if self.is_in_write_group():
2204
raise errors.InternalBzrError(
2205
"May not fetch while in a write group.")
2206
# fast path same-url fetch operations
2207
if (self.has_same_location(source) and
2208
fetch_spec is None and
2209
self._has_same_fallbacks(source)):
2210
# check that last_revision is in 'from' and then return a
2212
if (revision_id is not None
2213
and not _mod_revision.is_null(revision_id)):
2214
self.get_revision(revision_id)
2216
# if there is no specific appropriate InterRepository, this will get
2217
# the InterRepository base class, which raises an
2218
# IncompatibleRepositories when asked to fetch.
2219
inter = _mod_repository.InterRepository.get(source, self)
2220
if (fetch_spec is not None
2221
and not getattr(inter, "supports_fetch_spec", False)):
2222
raise errors.UnsupportedOperation(
2223
"fetch_spec not supported for %r" % inter)
2224
return inter.fetch(revision_id=revision_id,
2225
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2227
def create_bundle(self, target, base, fileobj, format=None):
2229
self._real_repository.create_bundle(target, base, fileobj, format)
2231
def fileids_altered_by_revision_ids(self, revision_ids):
2233
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2235
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2237
return self._real_repository._get_versioned_file_checker(
2238
revisions, revision_versions_cache)
2240
def _iter_files_bytes_rpc(self, desired_files, absent):
2241
path = self.controldir._path_for_remote_call(self._client)
2244
for (file_id, revid, identifier) in desired_files:
2245
lines.append(b''.join([
2246
osutils.safe_file_id(file_id),
2248
osutils.safe_revision_id(revid)]))
2249
identifiers.append(identifier)
2250
(response_tuple, response_handler) = (
2251
self._call_with_body_bytes_expecting_body(
2252
b"Repository.iter_files_bytes", (path, ), b"\n".join(lines)))
2253
if response_tuple != (b'ok', ):
2254
response_handler.cancel_read_body()
2255
raise errors.UnexpectedSmartServerResponse(response_tuple)
2256
byte_stream = response_handler.read_streamed_body()
2258
def decompress_stream(start, byte_stream, unused):
2259
decompressor = zlib.decompressobj()
2260
yield decompressor.decompress(start)
2261
while decompressor.unused_data == b"":
2263
data = next(byte_stream)
2264
except StopIteration:
2266
yield decompressor.decompress(data)
2267
yield decompressor.flush()
2268
unused.append(decompressor.unused_data)
2271
while not b"\n" in unused:
2273
unused += next(byte_stream)
2274
except StopIteration:
2276
header, rest = unused.split(b"\n", 1)
2277
args = header.split(b"\0")
2278
if args[0] == b"absent":
2279
absent[identifiers[int(args[3])]] = (args[1], args[2])
2282
elif args[0] == b"ok":
2285
raise errors.UnexpectedSmartServerResponse(args)
2287
yield (identifiers[idx],
2288
decompress_stream(rest, byte_stream, unused_chunks))
2289
unused = b"".join(unused_chunks)
2291
def iter_files_bytes(self, desired_files):
2292
"""See Repository.iter_file_bytes.
2296
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2297
desired_files, absent):
2298
yield identifier, bytes_iterator
2299
for fallback in self._fallback_repositories:
2302
desired_files = [(key[0], key[1], identifier)
2303
for identifier, key in viewitems(absent)]
2304
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2305
del absent[identifier]
2306
yield identifier, bytes_iterator
2308
# There may be more missing items, but raise an exception
2310
missing_identifier = next(iter(absent))
2311
missing_key = absent[missing_identifier]
2312
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2313
file_id=missing_key[0])
2314
except errors.UnknownSmartMethod:
2316
for (identifier, bytes_iterator) in (
2317
self._real_repository.iter_files_bytes(desired_files)):
2318
yield identifier, bytes_iterator
2320
def get_cached_parent_map(self, revision_ids):
2321
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2322
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2324
def get_parent_map(self, revision_ids):
2325
"""See breezy.Graph.get_parent_map()."""
2326
return self._make_parents_provider().get_parent_map(revision_ids)
2328
def _get_parent_map_rpc(self, keys):
2329
"""Helper for get_parent_map that performs the RPC."""
2330
medium = self._client._medium
2331
if medium._is_remote_before((1, 2)):
2332
# We already found out that the server can't understand
2333
# Repository.get_parent_map requests, so just fetch the whole
2336
# Note that this reads the whole graph, when only some keys are
2337
# wanted. On this old server there's no way (?) to get them all
2338
# in one go, and the user probably will have seen a warning about
2339
# the server being old anyhow.
2340
rg = self._get_revision_graph(None)
2341
# There is an API discrepancy between get_parent_map and
2342
# get_revision_graph. Specifically, a "key:()" pair in
2343
# get_revision_graph just means a node has no parents. For
2344
# "get_parent_map" it means the node is a ghost. So fix up the
2345
# graph to correct this.
2346
# https://bugs.launchpad.net/bzr/+bug/214894
2347
# There is one other "bug" which is that ghosts in
2348
# get_revision_graph() are not returned at all. But we won't worry
2349
# about that for now.
2350
for node_id, parent_ids in viewitems(rg):
2351
if parent_ids == ():
2352
rg[node_id] = (NULL_REVISION,)
2353
rg[NULL_REVISION] = ()
2358
raise ValueError('get_parent_map(None) is not valid')
2359
if NULL_REVISION in keys:
2360
keys.discard(NULL_REVISION)
2361
found_parents = {NULL_REVISION: ()}
2363
return found_parents
2366
# TODO(Needs analysis): We could assume that the keys being requested
2367
# from get_parent_map are in a breadth first search, so typically they
2368
# will all be depth N from some common parent, and we don't have to
2369
# have the server iterate from the root parent, but rather from the
2370
# keys we're searching; and just tell the server the keyspace we
2371
# already have; but this may be more traffic again.
2373
# Transform self._parents_map into a search request recipe.
2374
# TODO: Manage this incrementally to avoid covering the same path
2375
# repeatedly. (The server will have to on each request, but the less
2376
# work done the better).
2378
# Negative caching notes:
2379
# new server sends missing when a request including the revid
2380
# 'include-missing:' is present in the request.
2381
# missing keys are serialised as missing:X, and we then call
2382
# provider.note_missing(X) for-all X
2383
parents_map = self._unstacked_provider.get_cached_map()
2384
if parents_map is None:
2385
# Repository is not locked, so there's no cache.
2387
if _DEFAULT_SEARCH_DEPTH <= 0:
2388
(start_set, stop_keys,
2389
key_count) = vf_search.search_result_from_parent_map(
2390
parents_map, self._unstacked_provider.missing_keys)
2392
(start_set, stop_keys,
2393
key_count) = vf_search.limited_search_result_from_parent_map(
2394
parents_map, self._unstacked_provider.missing_keys,
2395
keys, depth=_DEFAULT_SEARCH_DEPTH)
2396
recipe = ('manual', start_set, stop_keys, key_count)
2397
body = self._serialise_search_recipe(recipe)
2398
path = self.controldir._path_for_remote_call(self._client)
2400
if not isinstance(key, bytes):
2402
"key %r not a bytes string" % (key,))
2403
verb = b'Repository.get_parent_map'
2404
args = (path, b'include-missing:') + tuple(keys)
2406
response = self._call_with_body_bytes_expecting_body(
2408
except errors.UnknownSmartMethod:
2409
# Server does not support this method, so get the whole graph.
2410
# Worse, we have to force a disconnection, because the server now
2411
# doesn't realise it has a body on the wire to consume, so the
2412
# only way to recover is to abandon the connection.
2414
'Server is too old for fast get_parent_map, reconnecting. '
2415
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2417
# To avoid having to disconnect repeatedly, we keep track of the
2418
# fact the server doesn't understand remote methods added in 1.2.
2419
medium._remember_remote_is_before((1, 2))
2420
# Recurse just once and we should use the fallback code.
2421
return self._get_parent_map_rpc(keys)
2422
response_tuple, response_handler = response
2423
if response_tuple[0] not in [b'ok']:
2424
response_handler.cancel_read_body()
2425
raise errors.UnexpectedSmartServerResponse(response_tuple)
2426
if response_tuple[0] == b'ok':
2427
coded = bz2.decompress(response_handler.read_body_bytes())
2429
# no revisions found
2431
lines = coded.split(b'\n')
2434
d = tuple(line.split())
2436
revision_graph[d[0]] = d[1:]
2439
if d[0].startswith(b'missing:'):
2441
self._unstacked_provider.note_missing_key(revid)
2443
# no parents - so give the Graph result
2445
revision_graph[d[0]] = (NULL_REVISION,)
2446
return revision_graph
2448
def get_signature_text(self, revision_id):
2449
with self.lock_read():
2450
path = self.controldir._path_for_remote_call(self._client)
2452
response_tuple, response_handler = self._call_expecting_body(
2453
b'Repository.get_revision_signature_text', path, revision_id)
2454
except errors.UnknownSmartMethod:
2456
return self._real_repository.get_signature_text(revision_id)
2457
except errors.NoSuchRevision as err:
2458
for fallback in self._fallback_repositories:
2460
return fallback.get_signature_text(revision_id)
2461
except errors.NoSuchRevision:
2465
if response_tuple[0] != b'ok':
2466
raise errors.UnexpectedSmartServerResponse(response_tuple)
2467
return response_handler.read_body_bytes()
2469
def _get_inventory_xml(self, revision_id):
2470
with self.lock_read():
2471
# This call is used by older working tree formats,
2472
# which stored a serialized basis inventory.
2474
return self._real_repository._get_inventory_xml(revision_id)
2476
def reconcile(self, other=None, thorough=False):
2477
from ..reconcile import RepoReconciler
2478
with self.lock_write():
2479
path = self.controldir._path_for_remote_call(self._client)
2481
response, handler = self._call_expecting_body(
2482
b'Repository.reconcile', path, self._lock_token)
2483
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2485
return self._real_repository.reconcile(other=other, thorough=thorough)
2486
if response != (b'ok', ):
2487
raise errors.UnexpectedSmartServerResponse(response)
2488
body = handler.read_body_bytes()
2489
result = RepoReconciler(self)
2490
for line in body.split(b'\n'):
2493
key, val_text = line.split(b':')
2494
if key == b"garbage_inventories":
2495
result.garbage_inventories = int(val_text)
2496
elif key == b"inconsistent_parents":
2497
result.inconsistent_parents = int(val_text)
2499
mutter("unknown reconcile key %r" % key)
2502
def all_revision_ids(self):
2503
path = self.controldir._path_for_remote_call(self._client)
2505
response_tuple, response_handler = self._call_expecting_body(
2506
b"Repository.all_revision_ids", path)
2507
except errors.UnknownSmartMethod:
2509
return self._real_repository.all_revision_ids()
2510
if response_tuple != (b"ok", ):
2511
raise errors.UnexpectedSmartServerResponse(response_tuple)
2512
revids = set(response_handler.read_body_bytes().splitlines())
2513
for fallback in self._fallback_repositories:
2514
revids.update(set(fallback.all_revision_ids()))
2517
def _filtered_revision_trees(self, revision_ids, file_ids):
2518
"""Return Tree for a revision on this branch with only some files.
2520
:param revision_ids: a sequence of revision-ids;
2521
a revision-id may not be None or b'null:'
2522
:param file_ids: if not None, the result is filtered
2523
so that only those file-ids, their parents and their
2524
children are included.
2526
inventories = self.iter_inventories(revision_ids)
2527
for inv in inventories:
2528
# Should we introduce a FilteredRevisionTree class rather
2529
# than pre-filter the inventory here?
2530
filtered_inv = inv.filter(file_ids)
2531
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2533
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2534
with self.lock_read():
2535
medium = self._client._medium
2536
if medium._is_remote_before((1, 2)):
2538
for delta in self._real_repository.get_deltas_for_revisions(
2539
revisions, specific_fileids):
2542
# Get the revision-ids of interest
2543
required_trees = set()
2544
for revision in revisions:
2545
required_trees.add(revision.revision_id)
2546
required_trees.update(revision.parent_ids[:1])
2548
# Get the matching filtered trees. Note that it's more
2549
# efficient to pass filtered trees to changes_from() rather
2550
# than doing the filtering afterwards. changes_from() could
2551
# arguably do the filtering itself but it's path-based, not
2552
# file-id based, so filtering before or afterwards is
2554
if specific_fileids is None:
2555
trees = dict((t.get_revision_id(), t) for
2556
t in self.revision_trees(required_trees))
2558
trees = dict((t.get_revision_id(), t) for
2559
t in self._filtered_revision_trees(required_trees,
2562
# Calculate the deltas
2563
for revision in revisions:
2564
if not revision.parent_ids:
2565
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2567
old_tree = trees[revision.parent_ids[0]]
2568
yield trees[revision.revision_id].changes_from(old_tree)
2570
def get_revision_delta(self, revision_id, specific_fileids=None):
2571
with self.lock_read():
2572
r = self.get_revision(revision_id)
2573
return list(self.get_deltas_for_revisions([r],
2574
specific_fileids=specific_fileids))[0]
2576
def revision_trees(self, revision_ids):
2577
with self.lock_read():
2578
inventories = self.iter_inventories(revision_ids)
2579
for inv in inventories:
2580
yield RemoteInventoryTree(self, inv, inv.revision_id)
2582
def get_revision_reconcile(self, revision_id):
2583
with self.lock_read():
2585
return self._real_repository.get_revision_reconcile(revision_id)
2587
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2588
with self.lock_read():
2590
return self._real_repository.check(revision_ids=revision_ids,
2591
callback_refs=callback_refs, check_repo=check_repo)
2593
def copy_content_into(self, destination, revision_id=None):
2594
"""Make a complete copy of the content in self into destination.
2596
This is a destructive operation! Do not use it on existing
2599
interrepo = _mod_repository.InterRepository.get(self, destination)
2600
return interrepo.copy_content(revision_id)
2602
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2603
# get a tarball of the remote repository, and copy from that into the
2606
# TODO: Maybe a progress bar while streaming the tarball?
2607
note(gettext("Copying repository content as tarball..."))
2608
tar_file = self._get_tarball('bz2')
2609
if tar_file is None:
2611
destination = to_bzrdir.create_repository()
2613
tar = tarfile.open('repository', fileobj=tar_file,
2615
tmpdir = osutils.mkdtemp()
2617
tar.extractall(tmpdir)
2618
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2619
tmp_repo = tmp_bzrdir.open_repository()
2620
tmp_repo.copy_content_into(destination, revision_id)
2622
osutils.rmtree(tmpdir)
2626
# TODO: Suggestion from john: using external tar is much faster than
2627
# python's tarfile library, but it may not work on windows.
2630
def inventories(self):
2631
"""Decorate the real repository for now.
2633
In the long term a full blown network facility is needed to
2634
avoid creating a real repository object locally.
2637
return self._real_repository.inventories
2639
def pack(self, hint=None, clean_obsolete_packs=False):
2640
"""Compress the data within the repository.
2645
body = b"".join([l.encode('ascii') + b"\n" for l in hint])
2646
with self.lock_write():
2647
path = self.controldir._path_for_remote_call(self._client)
2649
response, handler = self._call_with_body_bytes_expecting_body(
2650
b'Repository.pack', (path, self._lock_token,
2651
str(clean_obsolete_packs).encode('ascii')), body)
2652
except errors.UnknownSmartMethod:
2654
return self._real_repository.pack(hint=hint,
2655
clean_obsolete_packs=clean_obsolete_packs)
2656
handler.cancel_read_body()
2657
if response != (b'ok', ):
2658
raise errors.UnexpectedSmartServerResponse(response)
2661
def revisions(self):
2662
"""Decorate the real repository for now.
2664
In the long term a full blown network facility is needed.
2667
return self._real_repository.revisions
2669
def set_make_working_trees(self, new_value):
2671
new_value_str = b"True"
2673
new_value_str = b"False"
2674
path = self.controldir._path_for_remote_call(self._client)
2676
response = self._call(
2677
b'Repository.set_make_working_trees', path, new_value_str)
2678
except errors.UnknownSmartMethod:
2680
self._real_repository.set_make_working_trees(new_value)
2682
if response[0] != b'ok':
2683
raise errors.UnexpectedSmartServerResponse(response)
2686
def signatures(self):
2687
"""Decorate the real repository for now.
2689
In the long term a full blown network facility is needed to avoid
2690
creating a real repository object locally.
2693
return self._real_repository.signatures
2695
def sign_revision(self, revision_id, gpg_strategy):
2696
with self.lock_write():
2697
testament = _mod_testament.Testament.from_revision(
2699
plaintext = testament.as_short_text()
2700
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2704
"""Decorate the real repository for now.
2706
In the long term a full blown network facility is needed to avoid
2707
creating a real repository object locally.
2710
return self._real_repository.texts
2712
def _iter_revisions_rpc(self, revision_ids):
2713
body = b"\n".join(revision_ids)
2714
path = self.controldir._path_for_remote_call(self._client)
2715
response_tuple, response_handler = (
2716
self._call_with_body_bytes_expecting_body(
2717
b"Repository.iter_revisions", (path, ), body))
2718
if response_tuple[0] != b"ok":
2719
raise errors.UnexpectedSmartServerResponse(response_tuple)
2720
serializer_format = response_tuple[1].decode('ascii')
2721
serializer = serializer_format_registry.get(serializer_format)
2722
byte_stream = response_handler.read_streamed_body()
2723
decompressor = zlib.decompressobj()
2725
for bytes in byte_stream:
2726
chunks.append(decompressor.decompress(bytes))
2727
if decompressor.unused_data != b"":
2728
chunks.append(decompressor.flush())
2729
yield serializer.read_revision_from_string(b"".join(chunks))
2730
unused = decompressor.unused_data
2731
decompressor = zlib.decompressobj()
2732
chunks = [decompressor.decompress(unused)]
2733
chunks.append(decompressor.flush())
2734
text = b"".join(chunks)
2736
yield serializer.read_revision_from_string(b"".join(chunks))
2738
def iter_revisions(self, revision_ids):
2739
for rev_id in revision_ids:
2740
if not rev_id or not isinstance(rev_id, bytes):
2741
raise errors.InvalidRevisionId(
2742
revision_id=rev_id, branch=self)
2743
with self.lock_read():
2745
missing = set(revision_ids)
2746
for rev in self._iter_revisions_rpc(revision_ids):
2747
missing.remove(rev.revision_id)
2748
yield (rev.revision_id, rev)
2749
for fallback in self._fallback_repositories:
2752
for (revid, rev) in fallback.iter_revisions(missing):
2755
missing.remove(revid)
2756
for revid in missing:
2758
except errors.UnknownSmartMethod:
2760
for entry in self._real_repository.iter_revisions(revision_ids):
2763
def supports_rich_root(self):
2764
return self._format.rich_root_data
2767
def _serializer(self):
2768
return self._format._serializer
2770
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2771
with self.lock_write():
2772
signature = gpg_strategy.sign(plaintext, gpg.MODE_CLEAR)
2773
self.add_signature_text(revision_id, signature)
2775
def add_signature_text(self, revision_id, signature):
2776
if self._real_repository:
2777
# If there is a real repository the write group will
2778
# be in the real repository as well, so use that:
2780
return self._real_repository.add_signature_text(
2781
revision_id, signature)
2782
path = self.controldir._path_for_remote_call(self._client)
2783
response, handler = self._call_with_body_bytes_expecting_body(
2784
b'Repository.add_signature_text', (path, self._lock_token,
2786
tuple([token.encode('utf-8')
2787
for token in self._write_group_tokens]),
2789
handler.cancel_read_body()
2791
if response[0] != b'ok':
2792
raise errors.UnexpectedSmartServerResponse(response)
2793
self._write_group_tokens = [token.decode(
2794
'utf-8') for token in response[1:]]
2796
def has_signature_for_revision_id(self, revision_id):
2797
path = self.controldir._path_for_remote_call(self._client)
2799
response = self._call(b'Repository.has_signature_for_revision_id',
2801
except errors.UnknownSmartMethod:
2803
return self._real_repository.has_signature_for_revision_id(
2805
if response[0] not in (b'yes', b'no'):
2806
raise SmartProtocolError(
2807
'unexpected response code %s' % (response,))
2808
if response[0] == b'yes':
2810
for fallback in self._fallback_repositories:
2811
if fallback.has_signature_for_revision_id(revision_id):
2815
def verify_revision_signature(self, revision_id, gpg_strategy):
2816
with self.lock_read():
2817
if not self.has_signature_for_revision_id(revision_id):
2818
return gpg.SIGNATURE_NOT_SIGNED, None
2819
signature = self.get_signature_text(revision_id)
2821
testament = _mod_testament.Testament.from_revision(
2824
(status, key, signed_plaintext) = gpg_strategy.verify(signature)
2825
if testament.as_short_text() != signed_plaintext:
2826
return gpg.SIGNATURE_NOT_VALID, None
2827
return (status, key)
2829
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2831
return self._real_repository.item_keys_introduced_by(revision_ids,
2832
_files_pb=_files_pb)
2834
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2836
return self._real_repository._find_inconsistent_revision_parents(
2839
def _check_for_inconsistent_revision_parents(self):
2841
return self._real_repository._check_for_inconsistent_revision_parents()
2843
def _make_parents_provider(self, other=None):
2844
providers = [self._unstacked_provider]
2845
if other is not None:
2846
providers.insert(0, other)
2847
return graph.StackedParentsProvider(_LazyListJoin(
2848
providers, self._fallback_repositories))
2850
def _serialise_search_recipe(self, recipe):
2851
"""Serialise a graph search recipe.
2853
:param recipe: A search recipe (start, stop, count).
2854
:return: Serialised bytes.
2856
start_keys = b' '.join(recipe[1])
2857
stop_keys = b' '.join(recipe[2])
2858
count = str(recipe[3]).encode('ascii')
2859
return b'\n'.join((start_keys, stop_keys, count))
2861
def _serialise_search_result(self, search_result):
2862
parts = search_result.get_network_struct()
2863
return b'\n'.join(parts)
2866
path = self.controldir._path_for_remote_call(self._client)
2868
response = self._call(b'PackRepository.autopack', path)
2869
except errors.UnknownSmartMethod:
2871
self._real_repository._pack_collection.autopack()
2874
if response[0] != b'ok':
2875
raise errors.UnexpectedSmartServerResponse(response)
2877
def _revision_archive(self, revision_id, format, name, root, subdir,
2879
path = self.controldir._path_for_remote_call(self._client)
2880
format = format or ''
2882
subdir = subdir or ''
2883
force_mtime = int(force_mtime) if force_mtime is not None else None
2885
response, protocol = self._call_expecting_body(
2886
b'Repository.revision_archive', path,
2888
format.encode('ascii'),
2889
os.path.basename(name).encode('utf-8'),
2890
root.encode('utf-8'),
2891
subdir.encode('utf-8'),
2893
except errors.UnknownSmartMethod:
2895
if response[0] == b'ok':
2896
return iter([protocol.read_body_bytes()])
2897
raise errors.UnexpectedSmartServerResponse(response)
2899
def _annotate_file_revision(self, revid, tree_path, file_id, default_revision):
2900
path = self.controldir._path_for_remote_call(self._client)
2901
tree_path = tree_path.encode('utf-8')
2902
file_id = file_id or b''
2903
default_revision = default_revision or b''
2905
response, handler = self._call_expecting_body(
2906
b'Repository.annotate_file_revision', path,
2907
revid, tree_path, file_id, default_revision)
2908
except errors.UnknownSmartMethod:
2910
if response[0] != b'ok':
2911
raise errors.UnexpectedSmartServerResponse(response)
2912
return map(tuple, bencode.bdecode(handler.read_body_bytes()))
2915
class RemoteStreamSink(vf_repository.StreamSink):
2917
def _insert_real(self, stream, src_format, resume_tokens):
2918
self.target_repo._ensure_real()
2919
sink = self.target_repo._real_repository._get_sink()
2920
result = sink.insert_stream(stream, src_format, resume_tokens)
2922
self.target_repo.autopack()
2925
def insert_missing_keys(self, source, missing_keys):
2926
if (isinstance(source, RemoteStreamSource)
2927
and source.from_repository._client._medium == self.target_repo._client._medium):
2928
# Streaming from and to the same medium is tricky, since we don't support
2929
# more than one concurrent request. For now, just force VFS.
2930
stream = source._get_real_stream_for_missing_keys(missing_keys)
2932
stream = source.get_stream_for_missing_keys(missing_keys)
2933
return self.insert_stream_without_locking(stream,
2934
self.target_repo._format)
2936
def insert_stream(self, stream, src_format, resume_tokens):
2937
target = self.target_repo
2938
target._unstacked_provider.missing_keys.clear()
2939
candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
2940
if target._lock_token:
2941
candidate_calls.append(
2942
(b'Repository.insert_stream_locked', (1, 14)))
2943
lock_args = (target._lock_token or b'',)
2945
candidate_calls.append((b'Repository.insert_stream', (1, 13)))
2947
client = target._client
2948
medium = client._medium
2949
path = target.controldir._path_for_remote_call(client)
2950
# Probe for the verb to use with an empty stream before sending the
2951
# real stream to it. We do this both to avoid the risk of sending a
2952
# large request that is then rejected, and because we don't want to
2953
# implement a way to buffer, rewind, or restart the stream.
2955
for verb, required_version in candidate_calls:
2956
if medium._is_remote_before(required_version):
2959
# We've already done the probing (and set _is_remote_before) on
2960
# a previous insert.
2963
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2965
response = client.call_with_body_stream(
2966
(verb, path, b'') + lock_args, byte_stream)
2967
except errors.UnknownSmartMethod:
2968
medium._remember_remote_is_before(required_version)
2974
return self._insert_real(stream, src_format, resume_tokens)
2975
self._last_inv_record = None
2976
self._last_substream = None
2977
if required_version < (1, 19):
2978
# Remote side doesn't support inventory deltas. Wrap the stream to
2979
# make sure we don't send any. If the stream contains inventory
2980
# deltas we'll interrupt the smart insert_stream request and
2982
stream = self._stop_stream_if_inventory_delta(stream)
2983
byte_stream = smart_repo._stream_to_byte_stream(
2985
resume_tokens = b' '.join([token.encode('utf-8')
2986
for token in resume_tokens])
2987
response = client.call_with_body_stream(
2988
(verb, path, resume_tokens) + lock_args, byte_stream)
2989
if response[0][0] not in (b'ok', b'missing-basis'):
2990
raise errors.UnexpectedSmartServerResponse(response)
2991
if self._last_substream is not None:
2992
# The stream included an inventory-delta record, but the remote
2993
# side isn't new enough to support them. So we need to send the
2994
# rest of the stream via VFS.
2995
self.target_repo.refresh_data()
2996
return self._resume_stream_with_vfs(response, src_format)
2997
if response[0][0] == b'missing-basis':
2998
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2999
resume_tokens = [token.decode('utf-8') for token in tokens]
3000
return resume_tokens, set((entry[0].decode('utf-8'), ) + entry[1:] for entry in missing_keys)
3002
self.target_repo.refresh_data()
3005
def _resume_stream_with_vfs(self, response, src_format):
3006
"""Resume sending a stream via VFS, first resending the record and
3007
substream that couldn't be sent via an insert_stream verb.
3009
if response[0][0] == b'missing-basis':
3010
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3011
tokens = [token.decode('utf-8') for token in tokens]
3012
# Ignore missing_keys, we haven't finished inserting yet
3016
def resume_substream():
3017
# Yield the substream that was interrupted.
3018
for record in self._last_substream:
3020
self._last_substream = None
3022
def resume_stream():
3023
# Finish sending the interrupted substream
3024
yield ('inventory-deltas', resume_substream())
3025
# Then simply continue sending the rest of the stream.
3026
for substream_kind, substream in self._last_stream:
3027
yield substream_kind, substream
3028
return self._insert_real(resume_stream(), src_format, tokens)
3030
def _stop_stream_if_inventory_delta(self, stream):
3031
"""Normally this just lets the original stream pass-through unchanged.
3033
However if any 'inventory-deltas' substream occurs it will stop
3034
streaming, and store the interrupted substream and stream in
3035
self._last_substream and self._last_stream so that the stream can be
3036
resumed by _resume_stream_with_vfs.
3039
stream_iter = iter(stream)
3040
for substream_kind, substream in stream_iter:
3041
if substream_kind == 'inventory-deltas':
3042
self._last_substream = substream
3043
self._last_stream = stream_iter
3046
yield substream_kind, substream
3049
class RemoteStreamSource(vf_repository.StreamSource):
3050
"""Stream data from a remote server."""
3052
def get_stream(self, search):
3053
if (self.from_repository._fallback_repositories
3054
and self.to_format._fetch_order == 'topological'):
3055
return self._real_stream(self.from_repository, search)
3058
repos = [self.from_repository]
3064
repos.extend(repo._fallback_repositories)
3065
sources.append(repo)
3066
return self.missing_parents_chain(search, sources)
3068
def _get_real_stream_for_missing_keys(self, missing_keys):
3069
self.from_repository._ensure_real()
3070
real_repo = self.from_repository._real_repository
3071
real_source = real_repo._get_source(self.to_format)
3072
return real_source.get_stream_for_missing_keys(missing_keys)
3074
def get_stream_for_missing_keys(self, missing_keys):
3075
if not isinstance(self.from_repository, RemoteRepository):
3076
return self._get_real_stream_for_missing_keys(missing_keys)
3077
client = self.from_repository._client
3078
medium = client._medium
3079
if medium._is_remote_before((3, 0)):
3080
return self._get_real_stream_for_missing_keys(missing_keys)
3081
path = self.from_repository.controldir._path_for_remote_call(client)
3082
args = (path, self.to_format.network_name())
3083
search_bytes = b'\n'.join(
3084
[b'%s\t%s' % (key[0].encode('utf-8'), key[1]) for key in missing_keys])
3086
response, handler = self.from_repository._call_with_body_bytes_expecting_body(
3087
b'Repository.get_stream_for_missing_keys', args, search_bytes)
3088
except (errors.UnknownSmartMethod, errors.UnknownFormatError):
3089
return self._get_real_stream_for_missing_keys(missing_keys)
3090
if response[0] != b'ok':
3091
raise errors.UnexpectedSmartServerResponse(response)
3092
byte_stream = handler.read_streamed_body()
3093
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3094
self._record_counter)
3095
if src_format.network_name() != self.from_repository._format.network_name():
3096
raise AssertionError(
3097
"Mismatched RemoteRepository and stream src %r, %r" % (
3098
src_format.network_name(), repo._format.network_name()))
3101
def _real_stream(self, repo, search):
3102
"""Get a stream for search from repo.
3104
This never called RemoteStreamSource.get_stream, and is a helper
3105
for RemoteStreamSource._get_stream to allow getting a stream
3106
reliably whether fallback back because of old servers or trying
3107
to stream from a non-RemoteRepository (which the stacked support
3110
source = repo._get_source(self.to_format)
3111
if isinstance(source, RemoteStreamSource):
3113
source = repo._real_repository._get_source(self.to_format)
3114
return source.get_stream(search)
3116
def _get_stream(self, repo, search):
3117
"""Core worker to get a stream from repo for search.
3119
This is used by both get_stream and the stacking support logic. It
3120
deliberately gets a stream for repo which does not need to be
3121
self.from_repository. In the event that repo is not Remote, or
3122
cannot do a smart stream, a fallback is made to the generic
3123
repository._get_stream() interface, via self._real_stream.
3125
In the event of stacking, streams from _get_stream will not
3126
contain all the data for search - this is normal (see get_stream).
3128
:param repo: A repository.
3129
:param search: A search.
3131
# Fallbacks may be non-smart
3132
if not isinstance(repo, RemoteRepository):
3133
return self._real_stream(repo, search)
3134
client = repo._client
3135
medium = client._medium
3136
path = repo.controldir._path_for_remote_call(client)
3137
search_bytes = repo._serialise_search_result(search)
3138
args = (path, self.to_format.network_name())
3140
(b'Repository.get_stream_1.19', (1, 19)),
3141
(b'Repository.get_stream', (1, 13))]
3144
for verb, version in candidate_verbs:
3145
if medium._is_remote_before(version):
3148
response = repo._call_with_body_bytes_expecting_body(
3149
verb, args, search_bytes)
3150
except errors.UnknownSmartMethod:
3151
medium._remember_remote_is_before(version)
3152
except errors.UnknownErrorFromSmartServer as e:
3153
if isinstance(search, vf_search.EverythingResult):
3154
error_verb = e.error_from_smart_server.error_verb
3155
if error_verb == b'BadSearch':
3156
# Pre-2.4 servers don't support this sort of search.
3157
# XXX: perhaps falling back to VFS on BadSearch is a
3158
# good idea in general? It might provide a little bit
3159
# of protection against client-side bugs.
3160
medium._remember_remote_is_before((2, 4))
3164
response_tuple, response_handler = response
3168
return self._real_stream(repo, search)
3169
if response_tuple[0] != b'ok':
3170
raise errors.UnexpectedSmartServerResponse(response_tuple)
3171
byte_stream = response_handler.read_streamed_body()
3172
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3173
self._record_counter)
3174
if src_format.network_name() != repo._format.network_name():
3175
raise AssertionError(
3176
"Mismatched RemoteRepository and stream src %r, %r" % (
3177
src_format.network_name(), repo._format.network_name()))
3180
def missing_parents_chain(self, search, sources):
3181
"""Chain multiple streams together to handle stacking.
3183
:param search: The overall search to satisfy with streams.
3184
:param sources: A list of Repository objects to query.
3186
self.from_serialiser = self.from_repository._format._serializer
3187
self.seen_revs = set()
3188
self.referenced_revs = set()
3189
# If there are heads in the search, or the key count is > 0, we are not
3191
while not search.is_empty() and len(sources) > 1:
3192
source = sources.pop(0)
3193
stream = self._get_stream(source, search)
3194
for kind, substream in stream:
3195
if kind != 'revisions':
3196
yield kind, substream
3198
yield kind, self.missing_parents_rev_handler(substream)
3199
search = search.refine(self.seen_revs, self.referenced_revs)
3200
self.seen_revs = set()
3201
self.referenced_revs = set()
3202
if not search.is_empty():
3203
for kind, stream in self._get_stream(sources[0], search):
3206
def missing_parents_rev_handler(self, substream):
3207
for content in substream:
3208
revision_bytes = content.get_bytes_as('fulltext')
3209
revision = self.from_serialiser.read_revision_from_string(
3211
self.seen_revs.add(content.key[-1])
3212
self.referenced_revs.update(revision.parent_ids)
3216
class RemoteBranchLockableFiles(LockableFiles):
3217
"""A 'LockableFiles' implementation that talks to a smart server.
3219
This is not a public interface class.
3222
def __init__(self, bzrdir, _client):
3223
self.controldir = bzrdir
3224
self._client = _client
3225
self._need_find_modes = True
3226
LockableFiles.__init__(
3227
self, bzrdir.get_branch_transport(None),
3228
'lock', lockdir.LockDir)
3230
def _find_modes(self):
3231
# RemoteBranches don't let the client set the mode of control files.
3232
self._dir_mode = None
3233
self._file_mode = None
3236
class RemoteBranchFormat(branch.BranchFormat):
3238
def __init__(self, network_name=None):
3239
super(RemoteBranchFormat, self).__init__()
3240
self._matchingcontroldir = RemoteBzrDirFormat()
3241
self._matchingcontroldir.set_branch_format(self)
3242
self._custom_format = None
3243
self._network_name = network_name
3245
def __eq__(self, other):
3246
return (isinstance(other, RemoteBranchFormat)
3247
and self.__dict__ == other.__dict__)
3249
def _ensure_real(self):
3250
if self._custom_format is None:
3252
self._custom_format = branch.network_format_registry.get(
3255
raise errors.UnknownFormatError(kind='branch',
3256
format=self._network_name)
3258
def get_format_description(self):
3260
return 'Remote: ' + self._custom_format.get_format_description()
3262
def network_name(self):
3263
return self._network_name
3265
def open(self, a_controldir, name=None, ignore_fallbacks=False):
3266
return a_controldir.open_branch(name=name,
3267
ignore_fallbacks=ignore_fallbacks)
3269
def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3271
# Initialisation when using a local bzrdir object, or a non-vfs init
3272
# method is not available on the server.
3273
# self._custom_format is always set - the start of initialize ensures
3275
if isinstance(a_controldir, RemoteBzrDir):
3276
a_controldir._ensure_real()
3277
result = self._custom_format.initialize(a_controldir._real_bzrdir,
3278
name=name, append_revisions_only=append_revisions_only,
3279
repository=repository)
3281
# We assume the bzrdir is parameterised; it may not be.
3282
result = self._custom_format.initialize(a_controldir, name=name,
3283
append_revisions_only=append_revisions_only,
3284
repository=repository)
3285
if (isinstance(a_controldir, RemoteBzrDir)
3286
and not isinstance(result, RemoteBranch)):
3287
result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
3291
def initialize(self, a_controldir, name=None, repository=None,
3292
append_revisions_only=None):
3294
name = a_controldir._get_selected_branch()
3295
# 1) get the network name to use.
3296
if self._custom_format:
3297
network_name = self._custom_format.network_name()
3299
# Select the current breezy default and ask for that.
3300
reference_bzrdir_format = controldir.format_registry.get(
3302
reference_format = reference_bzrdir_format.get_branch_format()
3303
self._custom_format = reference_format
3304
network_name = reference_format.network_name()
3305
# Being asked to create on a non RemoteBzrDir:
3306
if not isinstance(a_controldir, RemoteBzrDir):
3307
return self._vfs_initialize(a_controldir, name=name,
3308
append_revisions_only=append_revisions_only,
3309
repository=repository)
3310
medium = a_controldir._client._medium
3311
if medium._is_remote_before((1, 13)):
3312
return self._vfs_initialize(a_controldir, name=name,
3313
append_revisions_only=append_revisions_only,
3314
repository=repository)
3315
# Creating on a remote bzr dir.
3316
# 2) try direct creation via RPC
3317
path = a_controldir._path_for_remote_call(a_controldir._client)
3319
# XXX JRV20100304: Support creating colocated branches
3320
raise errors.NoColocatedBranchSupport(self)
3321
verb = b'BzrDir.create_branch'
3323
response = a_controldir._call(verb, path, network_name)
3324
except errors.UnknownSmartMethod:
3325
# Fallback - use vfs methods
3326
medium._remember_remote_is_before((1, 13))
3327
return self._vfs_initialize(a_controldir, name=name,
3328
append_revisions_only=append_revisions_only,
3329
repository=repository)
3330
if response[0] != b'ok':
3331
raise errors.UnexpectedSmartServerResponse(response)
3332
# Turn the response into a RemoteRepository object.
3333
format = RemoteBranchFormat(network_name=response[1])
3334
repo_format = response_tuple_to_repo_format(response[3:])
3335
repo_path = response[2].decode('utf-8')
3336
if repository is not None:
3337
remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3338
url_diff = urlutils.relative_url(repository.user_url,
3341
raise AssertionError(
3342
'repository.user_url %r does not match URL from server '
3343
'response (%r + %r)'
3344
% (repository.user_url, a_controldir.user_url, repo_path))
3345
remote_repo = repository
3348
repo_bzrdir = a_controldir
3350
repo_bzrdir = RemoteBzrDir(
3351
a_controldir.root_transport.clone(
3352
repo_path), a_controldir._format,
3353
a_controldir._client)
3354
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3355
remote_branch = RemoteBranch(a_controldir, remote_repo,
3356
format=format, setup_stacking=False, name=name)
3357
if append_revisions_only:
3358
remote_branch.set_append_revisions_only(append_revisions_only)
3359
# XXX: We know this is a new branch, so it must have revno 0, revid
3360
# NULL_REVISION. Creating the branch locked would make this be unable
3361
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3362
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3363
return remote_branch
3365
def make_tags(self, branch):
3367
return self._custom_format.make_tags(branch)
3369
def supports_tags(self):
3370
# Remote branches might support tags, but we won't know until we
3371
# access the real remote branch.
3373
return self._custom_format.supports_tags()
3375
def supports_stacking(self):
3377
return self._custom_format.supports_stacking()
3379
def supports_set_append_revisions_only(self):
3381
return self._custom_format.supports_set_append_revisions_only()
3383
def _use_default_local_heads_to_fetch(self):
3384
# If the branch format is a metadir format *and* its heads_to_fetch
3385
# implementation is not overridden vs the base class, we can use the
3386
# base class logic rather than use the heads_to_fetch RPC. This is
3387
# usually cheaper in terms of net round trips, as the last-revision and
3388
# tags info fetched is cached and would be fetched anyway.
3390
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3391
branch_class = self._custom_format._branch_class()
3392
heads_to_fetch_impl = get_unbound_function(
3393
branch_class.heads_to_fetch)
3394
if heads_to_fetch_impl is get_unbound_function(branch.Branch.heads_to_fetch):
3399
class RemoteBranchStore(_mod_config.IniFileStore):
3400
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3402
Note that this is specific to bzr-based formats.
3405
def __init__(self, branch):
3406
super(RemoteBranchStore, self).__init__()
3407
self.branch = branch
3409
self._real_store = None
3411
def external_url(self):
3412
return urlutils.join(self.branch.user_url, 'branch.conf')
3414
def _load_content(self):
3415
path = self.branch._remote_path()
3417
response, handler = self.branch._call_expecting_body(
3418
b'Branch.get_config_file', path)
3419
except errors.UnknownSmartMethod:
3421
return self._real_store._load_content()
3422
if len(response) and response[0] != b'ok':
3423
raise errors.UnexpectedSmartServerResponse(response)
3424
return handler.read_body_bytes()
3426
def _save_content(self, content):
3427
path = self.branch._remote_path()
3429
response, handler = self.branch._call_with_body_bytes_expecting_body(
3430
b'Branch.put_config_file', (path,
3431
self.branch._lock_token, self.branch._repo_lock_token),
3433
except errors.UnknownSmartMethod:
3435
return self._real_store._save_content(content)
3436
handler.cancel_read_body()
3437
if response != (b'ok', ):
3438
raise errors.UnexpectedSmartServerResponse(response)
3440
def _ensure_real(self):
3441
self.branch._ensure_real()
3442
if self._real_store is None:
3443
self._real_store = _mod_config.BranchStore(self.branch)
3446
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3447
"""Branch stored on a server accessed by HPSS RPC.
3449
At the moment most operations are mapped down to simple file operations.
3452
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3453
_client=None, format=None, setup_stacking=True, name=None,
3454
possible_transports=None):
3455
"""Create a RemoteBranch instance.
3457
:param real_branch: An optional local implementation of the branch
3458
format, usually accessing the data via the VFS.
3459
:param _client: Private parameter for testing.
3460
:param format: A RemoteBranchFormat object, None to create one
3461
automatically. If supplied it should have a network_name already
3463
:param setup_stacking: If True make an RPC call to determine the
3464
stacked (or not) status of the branch. If False assume the branch
3466
:param name: Colocated branch name
3468
# We intentionally don't call the parent class's __init__, because it
3469
# will try to assign to self.tags, which is a property in this subclass.
3470
# And the parent's __init__ doesn't do much anyway.
3471
self.controldir = remote_bzrdir
3473
if _client is not None:
3474
self._client = _client
3476
self._client = remote_bzrdir._client
3477
self.repository = remote_repository
3478
if real_branch is not None:
3479
self._real_branch = real_branch
3480
# Give the remote repository the matching real repo.
3481
real_repo = self._real_branch.repository
3482
if isinstance(real_repo, RemoteRepository):
3483
real_repo._ensure_real()
3484
real_repo = real_repo._real_repository
3485
self.repository._set_real_repository(real_repo)
3486
# Give the branch the remote repository to let fast-pathing happen.
3487
self._real_branch.repository = self.repository
3489
self._real_branch = None
3490
# Fill out expected attributes of branch for breezy API users.
3491
self._clear_cached_state()
3492
# TODO: deprecate self.base in favor of user_url
3493
self.base = self.controldir.user_url
3495
self._control_files = None
3496
self._lock_mode = None
3497
self._lock_token = None
3498
self._repo_lock_token = None
3499
self._lock_count = 0
3500
self._leave_lock = False
3501
self.conf_store = None
3502
# Setup a format: note that we cannot call _ensure_real until all the
3503
# attributes above are set: This code cannot be moved higher up in this
3506
self._format = RemoteBranchFormat()
3507
if real_branch is not None:
3508
self._format._network_name = \
3509
self._real_branch._format.network_name()
3511
self._format = format
3512
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3513
# branch.open_branch method.
3514
self._real_ignore_fallbacks = not setup_stacking
3515
if not self._format._network_name:
3516
# Did not get from open_branchV2 - old server.
3518
self._format._network_name = \
3519
self._real_branch._format.network_name()
3520
self.tags = self._format.make_tags(self)
3521
# The base class init is not called, so we duplicate this:
3522
hooks = branch.Branch.hooks['open']
3525
self._is_stacked = False
3527
self._setup_stacking(possible_transports)
3529
def _setup_stacking(self, possible_transports):
3530
# configure stacking into the remote repository, by reading it from
3533
fallback_url = self.get_stacked_on_url()
3534
except (errors.NotStacked, branch.UnstackableBranchFormat,
3535
errors.UnstackableRepositoryFormat) as e:
3537
self._is_stacked = True
3538
if possible_transports is None:
3539
possible_transports = []
3541
possible_transports = list(possible_transports)
3542
possible_transports.append(self.controldir.root_transport)
3543
self._activate_fallback_location(fallback_url,
3544
possible_transports=possible_transports)
3546
def _get_config(self):
3547
return RemoteBranchConfig(self)
3549
def _get_config_store(self):
3550
if self.conf_store is None:
3551
self.conf_store = RemoteBranchStore(self)
3552
return self.conf_store
3554
def store_uncommitted(self, creator):
3556
return self._real_branch.store_uncommitted(creator)
3558
def get_unshelver(self, tree):
3560
return self._real_branch.get_unshelver(tree)
3562
def _get_real_transport(self):
3563
# if we try vfs access, return the real branch's vfs transport
3565
return self._real_branch._transport
3567
_transport = property(_get_real_transport)
3570
return "%s(%s)" % (self.__class__.__name__, self.base)
3574
def _ensure_real(self):
3575
"""Ensure that there is a _real_branch set.
3577
Used before calls to self._real_branch.
3579
if self._real_branch is None:
3580
if not vfs.vfs_enabled():
3581
raise AssertionError('smart server vfs must be enabled '
3582
'to use vfs implementation')
3583
self.controldir._ensure_real()
3584
self._real_branch = self.controldir._real_bzrdir.open_branch(
3585
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3586
# The remote branch and the real branch shares the same store. If
3587
# we don't, there will always be cases where one of the stores
3588
# doesn't see an update made on the other.
3589
self._real_branch.conf_store = self.conf_store
3590
if self.repository._real_repository is None:
3591
# Give the remote repository the matching real repo.
3592
real_repo = self._real_branch.repository
3593
if isinstance(real_repo, RemoteRepository):
3594
real_repo._ensure_real()
3595
real_repo = real_repo._real_repository
3596
self.repository._set_real_repository(real_repo)
3597
# Give the real branch the remote repository to let fast-pathing
3599
self._real_branch.repository = self.repository
3600
if self._lock_mode == 'r':
3601
self._real_branch.lock_read()
3602
elif self._lock_mode == 'w':
3603
self._real_branch.lock_write(token=self._lock_token)
3605
def _translate_error(self, err, **context):
3606
self.repository._translate_error(err, branch=self, **context)
3608
def _clear_cached_state(self):
3609
super(RemoteBranch, self)._clear_cached_state()
3610
self._tags_bytes = None
3611
if self._real_branch is not None:
3612
self._real_branch._clear_cached_state()
3614
def _clear_cached_state_of_remote_branch_only(self):
3615
"""Like _clear_cached_state, but doesn't clear the cache of
3618
This is useful when falling back to calling a method of
3619
self._real_branch that changes state. In that case the underlying
3620
branch changes, so we need to invalidate this RemoteBranch's cache of
3621
it. However, there's no need to invalidate the _real_branch's cache
3622
too, in fact doing so might harm performance.
3624
super(RemoteBranch, self)._clear_cached_state()
3627
def control_files(self):
3628
# Defer actually creating RemoteBranchLockableFiles until its needed,
3629
# because it triggers an _ensure_real that we otherwise might not need.
3630
if self._control_files is None:
3631
self._control_files = RemoteBranchLockableFiles(
3632
self.controldir, self._client)
3633
return self._control_files
3635
def get_physical_lock_status(self):
3636
"""See Branch.get_physical_lock_status()."""
3638
response = self._client.call(b'Branch.get_physical_lock_status',
3639
self._remote_path())
3640
except errors.UnknownSmartMethod:
3642
return self._real_branch.get_physical_lock_status()
3643
if response[0] not in (b'yes', b'no'):
3644
raise errors.UnexpectedSmartServerResponse(response)
3645
return (response[0] == b'yes')
3647
def get_stacked_on_url(self):
3648
"""Get the URL this branch is stacked against.
3650
:raises NotStacked: If the branch is not stacked.
3651
:raises UnstackableBranchFormat: If the branch does not support
3653
:raises UnstackableRepositoryFormat: If the repository does not support
3657
# there may not be a repository yet, so we can't use
3658
# self._translate_error, so we can't use self._call either.
3659
response = self._client.call(b'Branch.get_stacked_on_url',
3660
self._remote_path())
3661
except errors.ErrorFromSmartServer as err:
3662
# there may not be a repository yet, so we can't call through
3663
# its _translate_error
3664
_translate_error(err, branch=self)
3665
except errors.UnknownSmartMethod as err:
3667
return self._real_branch.get_stacked_on_url()
3668
if response[0] != b'ok':
3669
raise errors.UnexpectedSmartServerResponse(response)
3670
if sys.version_info[0] == 3:
3671
return response[1].decode('utf-8')
3674
def set_stacked_on_url(self, url):
3675
branch.Branch.set_stacked_on_url(self, url)
3676
# We need the stacked_on_url to be visible both locally (to not query
3677
# it repeatedly) and remotely (so smart verbs can get it server side)
3678
# Without the following line,
3679
# breezy.tests.per_branch.test_create_clone.TestCreateClone
3680
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3681
# fails for remote branches -- vila 2012-01-04
3682
self.conf_store.save_changes()
3684
self._is_stacked = False
3686
self._is_stacked = True
3688
def _vfs_get_tags_bytes(self):
3690
return self._real_branch._get_tags_bytes()
3692
def _get_tags_bytes(self):
3693
with self.lock_read():
3694
if self._tags_bytes is None:
3695
self._tags_bytes = self._get_tags_bytes_via_hpss()
3696
return self._tags_bytes
3698
def _get_tags_bytes_via_hpss(self):
3699
medium = self._client._medium
3700
if medium._is_remote_before((1, 13)):
3701
return self._vfs_get_tags_bytes()
3703
response = self._call(
3704
b'Branch.get_tags_bytes', self._remote_path())
3705
except errors.UnknownSmartMethod:
3706
medium._remember_remote_is_before((1, 13))
3707
return self._vfs_get_tags_bytes()
3710
def _vfs_set_tags_bytes(self, bytes):
3712
return self._real_branch._set_tags_bytes(bytes)
3714
def _set_tags_bytes(self, bytes):
3715
if self.is_locked():
3716
self._tags_bytes = bytes
3717
medium = self._client._medium
3718
if medium._is_remote_before((1, 18)):
3719
self._vfs_set_tags_bytes(bytes)
3723
self._remote_path(), self._lock_token, self._repo_lock_token)
3724
response = self._call_with_body_bytes(
3725
b'Branch.set_tags_bytes', args, bytes)
3726
except errors.UnknownSmartMethod:
3727
medium._remember_remote_is_before((1, 18))
3728
self._vfs_set_tags_bytes(bytes)
3730
def lock_read(self):
3731
"""Lock the branch for read operations.
3733
:return: A breezy.lock.LogicalLockResult.
3735
self.repository.lock_read()
3736
if not self._lock_mode:
3737
self._note_lock('r')
3738
self._lock_mode = 'r'
3739
self._lock_count = 1
3740
if self._real_branch is not None:
3741
self._real_branch.lock_read()
3743
self._lock_count += 1
3744
return lock.LogicalLockResult(self.unlock)
3746
def _remote_lock_write(self, token):
3748
branch_token = repo_token = b''
3750
branch_token = token
3751
repo_token = self.repository.lock_write().repository_token
3752
self.repository.unlock()
3753
err_context = {'token': token}
3755
response = self._call(
3756
b'Branch.lock_write', self._remote_path(), branch_token,
3757
repo_token or b'', **err_context)
3758
except errors.LockContention as e:
3759
# The LockContention from the server doesn't have any
3760
# information about the lock_url. We re-raise LockContention
3761
# with valid lock_url.
3762
raise errors.LockContention('(remote lock)',
3763
self.repository.base.split('.bzr/')[0])
3764
if response[0] != b'ok':
3765
raise errors.UnexpectedSmartServerResponse(response)
3766
ok, branch_token, repo_token = response
3767
return branch_token, repo_token
3769
def lock_write(self, token=None):
3770
if not self._lock_mode:
3771
self._note_lock('w')
3772
# Lock the branch and repo in one remote call.
3773
remote_tokens = self._remote_lock_write(token)
3774
self._lock_token, self._repo_lock_token = remote_tokens
3775
if not self._lock_token:
3776
raise SmartProtocolError(
3777
'Remote server did not return a token!')
3778
# Tell the self.repository object that it is locked.
3779
self.repository.lock_write(
3780
self._repo_lock_token, _skip_rpc=True)
3782
if self._real_branch is not None:
3783
self._real_branch.lock_write(token=self._lock_token)
3784
if token is not None:
3785
self._leave_lock = True
3787
self._leave_lock = False
3788
self._lock_mode = 'w'
3789
self._lock_count = 1
3790
elif self._lock_mode == 'r':
3791
raise errors.ReadOnlyError(self)
3793
if token is not None:
3794
# A token was given to lock_write, and we're relocking, so
3795
# check that the given token actually matches the one we
3797
if token != self._lock_token:
3798
raise errors.TokenMismatch(token, self._lock_token)
3799
self._lock_count += 1
3800
# Re-lock the repository too.
3801
self.repository.lock_write(self._repo_lock_token)
3802
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3804
def _unlock(self, branch_token, repo_token):
3805
err_context = {'token': str((branch_token, repo_token))}
3806
response = self._call(
3807
b'Branch.unlock', self._remote_path(), branch_token,
3808
repo_token or b'', **err_context)
3809
if response == (b'ok',):
3811
raise errors.UnexpectedSmartServerResponse(response)
3813
@only_raises(errors.LockNotHeld, errors.LockBroken)
3816
self._lock_count -= 1
3817
if not self._lock_count:
3818
if self.conf_store is not None:
3819
self.conf_store.save_changes()
3820
self._clear_cached_state()
3821
mode = self._lock_mode
3822
self._lock_mode = None
3823
if self._real_branch is not None:
3824
if (not self._leave_lock and mode == 'w'
3825
and self._repo_lock_token):
3826
# If this RemoteBranch will remove the physical lock
3827
# for the repository, make sure the _real_branch
3828
# doesn't do it first. (Because the _real_branch's
3829
# repository is set to be the RemoteRepository.)
3830
self._real_branch.repository.leave_lock_in_place()
3831
self._real_branch.unlock()
3833
# Only write-locked branched need to make a remote method
3834
# call to perform the unlock.
3836
if not self._lock_token:
3837
raise AssertionError('Locked, but no token!')
3838
branch_token = self._lock_token
3839
repo_token = self._repo_lock_token
3840
self._lock_token = None
3841
self._repo_lock_token = None
3842
if not self._leave_lock:
3843
self._unlock(branch_token, repo_token)
3845
self.repository.unlock()
3847
def break_lock(self):
3849
response = self._call(
3850
b'Branch.break_lock', self._remote_path())
3851
except errors.UnknownSmartMethod:
3853
return self._real_branch.break_lock()
3854
if response != (b'ok',):
3855
raise errors.UnexpectedSmartServerResponse(response)
3857
def leave_lock_in_place(self):
3858
if not self._lock_token:
3859
raise NotImplementedError(self.leave_lock_in_place)
3860
self._leave_lock = True
3862
def dont_leave_lock_in_place(self):
3863
if not self._lock_token:
3864
raise NotImplementedError(self.dont_leave_lock_in_place)
3865
self._leave_lock = False
3867
def get_rev_id(self, revno, history=None):
3869
return _mod_revision.NULL_REVISION
3870
with self.lock_read():
3871
last_revision_info = self.last_revision_info()
3872
ok, result = self.repository.get_rev_id_for_revno(
3873
revno, last_revision_info)
3876
missing_parent = result[1]
3877
# Either the revision named by the server is missing, or its parent
3878
# is. Call get_parent_map to determine which, so that we report a
3880
parent_map = self.repository.get_parent_map([missing_parent])
3881
if missing_parent in parent_map:
3882
missing_parent = parent_map[missing_parent]
3883
raise errors.RevisionNotPresent(missing_parent, self.repository)
3885
def _read_last_revision_info(self):
3886
response = self._call(
3887
b'Branch.last_revision_info', self._remote_path())
3888
if response[0] != b'ok':
3889
raise SmartProtocolError(
3890
'unexpected response code %s' % (response,))
3891
revno = int(response[1])
3892
last_revision = response[2]
3893
return (revno, last_revision)
3895
def _gen_revision_history(self):
3896
"""See Branch._gen_revision_history()."""
3897
if self._is_stacked:
3899
return self._real_branch._gen_revision_history()
3900
response_tuple, response_handler = self._call_expecting_body(
3901
b'Branch.revision_history', self._remote_path())
3902
if response_tuple[0] != b'ok':
3903
raise errors.UnexpectedSmartServerResponse(response_tuple)
3904
result = response_handler.read_body_bytes().split(b'\x00')
3909
def _remote_path(self):
3910
return self.controldir._path_for_remote_call(self._client)
3912
def _set_last_revision_descendant(self, revision_id, other_branch,
3913
allow_diverged=False, allow_overwrite_descendant=False):
3914
# This performs additional work to meet the hook contract; while its
3915
# undesirable, we have to synthesise the revno to call the hook, and
3916
# not calling the hook is worse as it means changes can't be prevented.
3917
# Having calculated this though, we can't just call into
3918
# set_last_revision_info as a simple call, because there is a set_rh
3919
# hook that some folk may still be using.
3920
old_revno, old_revid = self.last_revision_info()
3921
history = self._lefthand_history(revision_id)
3922
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3923
err_context = {'other_branch': other_branch}
3924
response = self._call(b'Branch.set_last_revision_ex',
3925
self._remote_path(), self._lock_token, self._repo_lock_token,
3926
revision_id, int(allow_diverged), int(
3927
allow_overwrite_descendant),
3929
self._clear_cached_state()
3930
if len(response) != 3 and response[0] != b'ok':
3931
raise errors.UnexpectedSmartServerResponse(response)
3932
new_revno, new_revision_id = response[1:]
3933
self._last_revision_info_cache = new_revno, new_revision_id
3934
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3935
if self._real_branch is not None:
3936
cache = new_revno, new_revision_id
3937
self._real_branch._last_revision_info_cache = cache
3939
def _set_last_revision(self, revision_id):
3940
old_revno, old_revid = self.last_revision_info()
3941
# This performs additional work to meet the hook contract; while its
3942
# undesirable, we have to synthesise the revno to call the hook, and
3943
# not calling the hook is worse as it means changes can't be prevented.
3944
# Having calculated this though, we can't just call into
3945
# set_last_revision_info as a simple call, because there is a set_rh
3946
# hook that some folk may still be using.
3947
history = self._lefthand_history(revision_id)
3948
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3949
self._clear_cached_state()
3950
response = self._call(b'Branch.set_last_revision',
3951
self._remote_path(), self._lock_token, self._repo_lock_token,
3953
if response != (b'ok',):
3954
raise errors.UnexpectedSmartServerResponse(response)
3955
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3957
def _get_parent_location(self):
3958
medium = self._client._medium
3959
if medium._is_remote_before((1, 13)):
3960
return self._vfs_get_parent_location()
3962
response = self._call(b'Branch.get_parent', self._remote_path())
3963
except errors.UnknownSmartMethod:
3964
medium._remember_remote_is_before((1, 13))
3965
return self._vfs_get_parent_location()
3966
if len(response) != 1:
3967
raise errors.UnexpectedSmartServerResponse(response)
3968
parent_location = response[0]
3969
if parent_location == b'':
3971
return parent_location.decode('utf-8')
3973
def _vfs_get_parent_location(self):
3975
return self._real_branch._get_parent_location()
3977
def _set_parent_location(self, url):
3978
medium = self._client._medium
3979
if medium._is_remote_before((1, 15)):
3980
return self._vfs_set_parent_location(url)
3982
call_url = url or u''
3983
if isinstance(call_url, text_type):
3984
call_url = call_url.encode('utf-8')
3985
response = self._call(b'Branch.set_parent_location',
3986
self._remote_path(), self._lock_token, self._repo_lock_token,
3988
except errors.UnknownSmartMethod:
3989
medium._remember_remote_is_before((1, 15))
3990
return self._vfs_set_parent_location(url)
3992
raise errors.UnexpectedSmartServerResponse(response)
3994
def _vfs_set_parent_location(self, url):
3996
return self._real_branch._set_parent_location(url)
3998
def pull(self, source, overwrite=False, stop_revision=None,
4000
with self.lock_write():
4001
self._clear_cached_state_of_remote_branch_only()
4003
return self._real_branch.pull(
4004
source, overwrite=overwrite, stop_revision=stop_revision,
4005
_override_hook_target=self, **kwargs)
4007
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
4008
with self.lock_read():
4010
return self._real_branch.push(
4011
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
4012
_override_hook_source_branch=self)
4014
def peek_lock_mode(self):
4015
return self._lock_mode
4017
def is_locked(self):
4018
return self._lock_count >= 1
4020
def revision_id_to_dotted_revno(self, revision_id):
4021
"""Given a revision id, return its dotted revno.
4023
:return: a tuple like (1,) or (400,1,3).
4025
with self.lock_read():
4027
response = self._call(b'Branch.revision_id_to_revno',
4028
self._remote_path(), revision_id)
4029
except errors.UnknownSmartMethod:
4031
return self._real_branch.revision_id_to_dotted_revno(revision_id)
4032
if response[0] == b'ok':
4033
return tuple([int(x) for x in response[1:]])
4035
raise errors.UnexpectedSmartServerResponse(response)
4037
def revision_id_to_revno(self, revision_id):
4038
"""Given a revision id on the branch mainline, return its revno.
4042
with self.lock_read():
4044
response = self._call(b'Branch.revision_id_to_revno',
4045
self._remote_path(), revision_id)
4046
except errors.UnknownSmartMethod:
4048
return self._real_branch.revision_id_to_revno(revision_id)
4049
if response[0] == b'ok':
4050
if len(response) == 2:
4051
return int(response[1])
4052
raise NoSuchRevision(self, revision_id)
4054
raise errors.UnexpectedSmartServerResponse(response)
4056
def set_last_revision_info(self, revno, revision_id):
4057
with self.lock_write():
4058
# XXX: These should be returned by the set_last_revision_info verb
4059
old_revno, old_revid = self.last_revision_info()
4060
self._run_pre_change_branch_tip_hooks(revno, revision_id)
4061
if not revision_id or not isinstance(revision_id, bytes):
4062
raise errors.InvalidRevisionId(
4063
revision_id=revision_id, branch=self)
4065
response = self._call(b'Branch.set_last_revision_info',
4066
self._remote_path(), self._lock_token, self._repo_lock_token,
4067
str(revno).encode('ascii'), revision_id)
4068
except errors.UnknownSmartMethod:
4070
self._clear_cached_state_of_remote_branch_only()
4071
self._real_branch.set_last_revision_info(revno, revision_id)
4072
self._last_revision_info_cache = revno, revision_id
4074
if response == (b'ok',):
4075
self._clear_cached_state()
4076
self._last_revision_info_cache = revno, revision_id
4077
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
4078
# Update the _real_branch's cache too.
4079
if self._real_branch is not None:
4080
cache = self._last_revision_info_cache
4081
self._real_branch._last_revision_info_cache = cache
4083
raise errors.UnexpectedSmartServerResponse(response)
4085
def generate_revision_history(self, revision_id, last_rev=None,
4087
with self.lock_write():
4088
medium = self._client._medium
4089
if not medium._is_remote_before((1, 6)):
4090
# Use a smart method for 1.6 and above servers
4092
self._set_last_revision_descendant(revision_id, other_branch,
4093
allow_diverged=True, allow_overwrite_descendant=True)
4095
except errors.UnknownSmartMethod:
4096
medium._remember_remote_is_before((1, 6))
4097
self._clear_cached_state_of_remote_branch_only()
4098
graph = self.repository.get_graph()
4099
(last_revno, last_revid) = self.last_revision_info()
4100
known_revision_ids = [
4101
(last_revid, last_revno),
4102
(_mod_revision.NULL_REVISION, 0),
4104
if last_rev is not None:
4105
if not graph.is_ancestor(last_rev, revision_id):
4106
# our previous tip is not merged into stop_revision
4107
raise errors.DivergedBranches(self, other_branch)
4108
revno = graph.find_distance_to_null(
4109
revision_id, known_revision_ids)
4110
self.set_last_revision_info(revno, revision_id)
4112
def set_push_location(self, location):
4113
self._set_config_location('push_location', location)
4115
def heads_to_fetch(self):
4116
if self._format._use_default_local_heads_to_fetch():
4117
# We recognise this format, and its heads-to-fetch implementation
4118
# is the default one (tip + tags). In this case it's cheaper to
4119
# just use the default implementation rather than a special RPC as
4120
# the tip and tags data is cached.
4121
return branch.Branch.heads_to_fetch(self)
4122
medium = self._client._medium
4123
if medium._is_remote_before((2, 4)):
4124
return self._vfs_heads_to_fetch()
4126
return self._rpc_heads_to_fetch()
4127
except errors.UnknownSmartMethod:
4128
medium._remember_remote_is_before((2, 4))
4129
return self._vfs_heads_to_fetch()
4131
def _rpc_heads_to_fetch(self):
4132
response = self._call(b'Branch.heads_to_fetch', self._remote_path())
4133
if len(response) != 2:
4134
raise errors.UnexpectedSmartServerResponse(response)
4135
must_fetch, if_present_fetch = response
4136
return set(must_fetch), set(if_present_fetch)
4138
def _vfs_heads_to_fetch(self):
4140
return self._real_branch.heads_to_fetch()
4143
class RemoteConfig(object):
4144
"""A Config that reads and writes from smart verbs.
4146
It is a low-level object that considers config data to be name/value pairs
4147
that may be associated with a section. Assigning meaning to the these
4148
values is done at higher levels like breezy.config.TreeConfig.
4151
def get_option(self, name, section=None, default=None):
4152
"""Return the value associated with a named option.
4154
:param name: The name of the value
4155
:param section: The section the option is in (if any)
4156
:param default: The value to return if the value is not set
4157
:return: The value or default value
4160
configobj = self._get_configobj()
4163
section_obj = configobj
4166
section_obj = configobj[section]
4169
if section_obj is None:
4172
value = section_obj.get(name, default)
4173
except errors.UnknownSmartMethod:
4174
value = self._vfs_get_option(name, section, default)
4175
for hook in _mod_config.OldConfigHooks['get']:
4176
hook(self, name, value)
4179
def _response_to_configobj(self, response):
4180
if len(response[0]) and response[0][0] != b'ok':
4181
raise errors.UnexpectedSmartServerResponse(response)
4182
lines = response[1].read_body_bytes().splitlines()
4183
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4184
for hook in _mod_config.OldConfigHooks['load']:
4189
class RemoteBranchConfig(RemoteConfig):
4190
"""A RemoteConfig for Branches."""
4192
def __init__(self, branch):
4193
self._branch = branch
4195
def _get_configobj(self):
4196
path = self._branch._remote_path()
4197
response = self._branch._client.call_expecting_body(
4198
b'Branch.get_config_file', path)
4199
return self._response_to_configobj(response)
4201
def set_option(self, value, name, section=None):
4202
"""Set the value associated with a named option.
4204
:param value: The value to set
4205
:param name: The name of the value to set
4206
:param section: The section the option is in (if any)
4208
medium = self._branch._client._medium
4209
if medium._is_remote_before((1, 14)):
4210
return self._vfs_set_option(value, name, section)
4211
if isinstance(value, dict):
4212
if medium._is_remote_before((2, 2)):
4213
return self._vfs_set_option(value, name, section)
4214
return self._set_config_option_dict(value, name, section)
4216
return self._set_config_option(value, name, section)
4218
def _set_config_option(self, value, name, section):
4220
path = self._branch._remote_path()
4221
response = self._branch._client.call(b'Branch.set_config_option',
4222
path, self._branch._lock_token, self._branch._repo_lock_token,
4224
'utf8'), name.encode('utf-8'),
4225
(section or '').encode('utf-8'))
4226
except errors.UnknownSmartMethod:
4227
medium = self._branch._client._medium
4228
medium._remember_remote_is_before((1, 14))
4229
return self._vfs_set_option(value, name, section)
4231
raise errors.UnexpectedSmartServerResponse(response)
4233
def _serialize_option_dict(self, option_dict):
4235
for key, value in option_dict.items():
4236
if isinstance(key, text_type):
4237
key = key.encode('utf8')
4238
if isinstance(value, text_type):
4239
value = value.encode('utf8')
4240
utf8_dict[key] = value
4241
return bencode.bencode(utf8_dict)
4243
def _set_config_option_dict(self, value, name, section):
4245
path = self._branch._remote_path()
4246
serialised_dict = self._serialize_option_dict(value)
4247
response = self._branch._client.call(
4248
b'Branch.set_config_option_dict',
4249
path, self._branch._lock_token, self._branch._repo_lock_token,
4250
serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
4251
except errors.UnknownSmartMethod:
4252
medium = self._branch._client._medium
4253
medium._remember_remote_is_before((2, 2))
4254
return self._vfs_set_option(value, name, section)
4256
raise errors.UnexpectedSmartServerResponse(response)
4258
def _real_object(self):
4259
self._branch._ensure_real()
4260
return self._branch._real_branch
4262
def _vfs_set_option(self, value, name, section=None):
4263
return self._real_object()._get_config().set_option(
4264
value, name, section)
4267
class RemoteBzrDirConfig(RemoteConfig):
4268
"""A RemoteConfig for BzrDirs."""
4270
def __init__(self, bzrdir):
4271
self._bzrdir = bzrdir
4273
def _get_configobj(self):
4274
medium = self._bzrdir._client._medium
4275
verb = b'BzrDir.get_config_file'
4276
if medium._is_remote_before((1, 15)):
4277
raise errors.UnknownSmartMethod(verb)
4278
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4279
response = self._bzrdir._call_expecting_body(
4281
return self._response_to_configobj(response)
4283
def _vfs_get_option(self, name, section, default):
4284
return self._real_object()._get_config().get_option(
4285
name, section, default)
4287
def set_option(self, value, name, section=None):
4288
"""Set the value associated with a named option.
4290
:param value: The value to set
4291
:param name: The name of the value to set
4292
:param section: The section the option is in (if any)
4294
return self._real_object()._get_config().set_option(
4295
value, name, section)
4297
def _real_object(self):
4298
self._bzrdir._ensure_real()
4299
return self._bzrdir._real_bzrdir
4302
error_translators = registry.Registry()
4303
no_context_error_translators = registry.Registry()
4306
def _translate_error(err, **context):
4307
"""Translate an ErrorFromSmartServer into a more useful error.
4309
Possible context keys:
4317
If the error from the server doesn't match a known pattern, then
4318
UnknownErrorFromSmartServer is raised.
4322
return context[name]
4324
mutter('Missing key \'%s\' in context %r', name, context)
4328
"""Get the path from the context if present, otherwise use first error
4332
return context['path']
4335
return err.error_args[0].decode('utf-8')
4337
mutter('Missing key \'path\' in context %r', context)
4339
if not isinstance(err.error_verb, bytes):
4340
raise TypeError(err.error_verb)
4342
translator = error_translators.get(err.error_verb)
4346
raise translator(err, find, get_path)
4348
translator = no_context_error_translators.get(err.error_verb)
4350
raise errors.UnknownErrorFromSmartServer(err)
4352
raise translator(err)
4355
error_translators.register(b'NoSuchRevision',
4356
lambda err, find, get_path: NoSuchRevision(
4357
find('branch'), err.error_args[0]))
4358
error_translators.register(b'nosuchrevision',
4359
lambda err, find, get_path: NoSuchRevision(
4360
find('repository'), err.error_args[0]))
4363
def _translate_nobranch_error(err, find, get_path):
4364
if len(err.error_args) >= 1:
4365
extra = err.error_args[0].decode('utf-8')
4368
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4372
error_translators.register(b'nobranch', _translate_nobranch_error)
4373
error_translators.register(b'norepository',
4374
lambda err, find, get_path: errors.NoRepositoryPresent(
4376
error_translators.register(b'UnlockableTransport',
4377
lambda err, find, get_path: errors.UnlockableTransport(
4378
find('bzrdir').root_transport))
4379
error_translators.register(b'TokenMismatch',
4380
lambda err, find, get_path: errors.TokenMismatch(
4381
find('token'), '(remote token)'))
4382
error_translators.register(b'Diverged',
4383
lambda err, find, get_path: errors.DivergedBranches(
4384
find('branch'), find('other_branch')))
4385
error_translators.register(b'NotStacked',
4386
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4389
def _translate_PermissionDenied(err, find, get_path):
4391
if len(err.error_args) >= 2:
4392
extra = err.error_args[1].decode('utf-8')
4395
return errors.PermissionDenied(path, extra=extra)
4398
error_translators.register(b'PermissionDenied', _translate_PermissionDenied)
4399
error_translators.register(b'ReadError',
4400
lambda err, find, get_path: errors.ReadError(get_path()))
4401
error_translators.register(b'NoSuchFile',
4402
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4403
error_translators.register(b'TokenLockingNotSupported',
4404
lambda err, find, get_path: errors.TokenLockingNotSupported(
4405
find('repository')))
4406
error_translators.register(b'UnsuspendableWriteGroup',
4407
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4408
repository=find('repository')))
4409
error_translators.register(b'UnresumableWriteGroup',
4410
lambda err, find, get_path: errors.UnresumableWriteGroup(
4411
repository=find('repository'), write_groups=err.error_args[0],
4412
reason=err.error_args[1]))
4413
no_context_error_translators.register(b'GhostRevisionsHaveNoRevno',
4414
lambda err: errors.GhostRevisionsHaveNoRevno(*err.error_args))
4415
no_context_error_translators.register(b'IncompatibleRepositories',
4416
lambda err: errors.IncompatibleRepositories(
4417
err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8'), err.error_args[2].decode('utf-8')))
4418
no_context_error_translators.register(b'LockContention',
4419
lambda err: errors.LockContention('(remote lock)'))
4420
no_context_error_translators.register(b'LockFailed',
4421
lambda err: errors.LockFailed(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4422
no_context_error_translators.register(b'TipChangeRejected',
4423
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4424
no_context_error_translators.register(b'UnstackableBranchFormat',
4425
lambda err: branch.UnstackableBranchFormat(*err.error_args))
4426
no_context_error_translators.register(b'UnstackableRepositoryFormat',
4427
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4428
no_context_error_translators.register(b'FileExists',
4429
lambda err: errors.FileExists(err.error_args[0].decode('utf-8')))
4430
no_context_error_translators.register(b'DirectoryNotEmpty',
4431
lambda err: errors.DirectoryNotEmpty(err.error_args[0].decode('utf-8')))
4432
no_context_error_translators.register(b'UnknownFormat',
4433
lambda err: errors.UnknownFormatError(
4434
err.error_args[0].decode('ascii'), err.error_args[0].decode('ascii')))
4435
no_context_error_translators.register(b'InvalidURL',
4436
lambda err: urlutils.InvalidURL(
4437
err.error_args[0].decode('utf-8'), err.error_args[1].decode('ascii')))
4440
def _translate_short_readv_error(err):
4441
args = err.error_args
4442
return errors.ShortReadvError(
4443
args[0].decode('utf-8'),
4444
int(args[1].decode('ascii')), int(args[2].decode('ascii')),
4445
int(args[3].decode('ascii')))
4448
no_context_error_translators.register(b'ShortReadvError',
4449
_translate_short_readv_error)
4452
def _translate_unicode_error(err):
4453
encoding = err.error_args[0].decode('ascii')
4454
val = err.error_args[1].decode('utf-8')
4455
start = int(err.error_args[2].decode('ascii'))
4456
end = int(err.error_args[3].decode('ascii'))
4457
reason = err.error_args[4].decode('utf-8')
4458
if val.startswith('u:'):
4459
val = val[2:].decode('utf-8')
4460
elif val.startswith('s:'):
4461
val = val[2:].decode('base64')
4462
if err.error_verb == 'UnicodeDecodeError':
4463
raise UnicodeDecodeError(encoding, val, start, end, reason)
4464
elif err.error_verb == 'UnicodeEncodeError':
4465
raise UnicodeEncodeError(encoding, val, start, end, reason)
4468
no_context_error_translators.register(b'UnicodeEncodeError',
4469
_translate_unicode_error)
4470
no_context_error_translators.register(b'UnicodeDecodeError',
4471
_translate_unicode_error)
4472
no_context_error_translators.register(b'ReadOnlyError',
4473
lambda err: errors.TransportNotPossible('readonly transport'))
4474
no_context_error_translators.register(b'MemoryError',
4475
lambda err: errors.BzrError("remote server out of memory\n"
4476
"Retry non-remotely, or contact the server admin for details."))
4477
no_context_error_translators.register(b'RevisionNotPresent',
4478
lambda err: errors.RevisionNotPresent(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4480
no_context_error_translators.register(b'BzrCheckError',
4481
lambda err: errors.BzrCheckError(msg=err.error_args[0].decode('utf-8')))