89
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
116
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.bzr.remote
117
# does not have to be imported unless a remote format is involved.
119
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
120
"""Format representing bzrdirs accessed via a smart server"""
122
supports_workingtrees = False
124
colocated_branches = False
127
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
128
# XXX: It's a bit ugly that the network name is here, because we'd
129
# like to believe that format objects are stateless or at least
130
# immutable, However, we do at least avoid mutating the name after
131
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
132
self._network_name = None
135
return "%s(_network_name=%r)" % (self.__class__.__name__,
138
def get_format_description(self):
139
if self._network_name:
141
real_format = controldir.network_format_registry.get(
146
return 'Remote: ' + real_format.get_format_description()
147
return 'bzr remote bzrdir'
149
def get_format_string(self):
150
raise NotImplementedError(self.get_format_string)
152
def network_name(self):
153
if self._network_name:
154
return self._network_name
156
raise AssertionError("No network name set.")
158
def initialize_on_transport(self, transport):
160
# hand off the request to the smart server
161
client_medium = transport.get_smart_medium()
162
except errors.NoSmartMedium:
163
# TODO: lookup the local format from a server hint.
164
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
165
return local_dir_format.initialize_on_transport(transport)
166
client = _SmartClient(client_medium)
167
path = client.remote_path_from_transport(transport)
169
response = client.call('BzrDirFormat.initialize', path)
170
except errors.ErrorFromSmartServer as err:
171
_translate_error(err, path=path)
172
if response[0] != 'ok':
173
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
174
format = RemoteBzrDirFormat()
175
self._supply_sub_formats_to(format)
176
return RemoteBzrDir(transport, format)
178
def parse_NoneTrueFalse(self, arg):
185
raise AssertionError("invalid arg %r" % arg)
187
def _serialize_NoneTrueFalse(self, arg):
194
def _serialize_NoneString(self, arg):
197
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
198
create_prefix=False, force_new_repo=False, stacked_on=None,
199
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
202
# hand off the request to the smart server
203
client_medium = transport.get_smart_medium()
204
except errors.NoSmartMedium:
207
# Decline to open it if the server doesn't support our required
208
# version (3) so that the VFS-based transport will do it.
209
if client_medium.should_probe():
211
server_version = client_medium.protocol_version()
212
if server_version != '2':
216
except errors.SmartProtocolError:
217
# Apparently there's no usable smart server there, even though
218
# the medium supports the smart protocol.
223
client = _SmartClient(client_medium)
224
path = client.remote_path_from_transport(transport)
225
if client_medium._is_remote_before((1, 16)):
228
# TODO: lookup the local format from a server hint.
229
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
230
self._supply_sub_formats_to(local_dir_format)
231
return local_dir_format.initialize_on_transport_ex(transport,
232
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
233
force_new_repo=force_new_repo, stacked_on=stacked_on,
234
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
235
make_working_trees=make_working_trees, shared_repo=shared_repo,
237
return self._initialize_on_transport_ex_rpc(client, path, transport,
238
use_existing_dir, create_prefix, force_new_repo, stacked_on,
239
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
241
def _initialize_on_transport_ex_rpc(self, client, path, transport,
242
use_existing_dir, create_prefix, force_new_repo, stacked_on,
243
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
245
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
246
args.append(self._serialize_NoneTrueFalse(create_prefix))
247
args.append(self._serialize_NoneTrueFalse(force_new_repo))
248
args.append(self._serialize_NoneString(stacked_on))
249
# stack_on_pwd is often/usually our transport
252
stack_on_pwd = transport.relpath(stack_on_pwd)
255
except errors.PathNotChild:
257
args.append(self._serialize_NoneString(stack_on_pwd))
258
args.append(self._serialize_NoneString(repo_format_name))
259
args.append(self._serialize_NoneTrueFalse(make_working_trees))
260
args.append(self._serialize_NoneTrueFalse(shared_repo))
261
request_network_name = self._network_name or \
262
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
264
response = client.call('BzrDirFormat.initialize_ex_1.16',
265
request_network_name, path, *args)
266
except errors.UnknownSmartMethod:
267
client._medium._remember_remote_is_before((1, 16))
268
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
269
self._supply_sub_formats_to(local_dir_format)
270
return local_dir_format.initialize_on_transport_ex(transport,
271
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
272
force_new_repo=force_new_repo, stacked_on=stacked_on,
273
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
274
make_working_trees=make_working_trees, shared_repo=shared_repo,
276
except errors.ErrorFromSmartServer as err:
277
_translate_error(err, path=path)
278
repo_path = response[0]
279
bzrdir_name = response[6]
280
require_stacking = response[7]
281
require_stacking = self.parse_NoneTrueFalse(require_stacking)
282
format = RemoteBzrDirFormat()
283
format._network_name = bzrdir_name
284
self._supply_sub_formats_to(format)
285
bzrdir = RemoteBzrDir(transport, format, _client=client)
287
repo_format = response_tuple_to_repo_format(response[1:])
291
repo_bzrdir_format = RemoteBzrDirFormat()
292
repo_bzrdir_format._network_name = response[5]
293
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
297
final_stack = response[8] or None
298
final_stack_pwd = response[9] or None
300
final_stack_pwd = urlutils.join(
301
transport.base, final_stack_pwd)
302
remote_repo = RemoteRepository(repo_bzr, repo_format)
303
if len(response) > 10:
304
# Updated server verb that locks remotely.
305
repo_lock_token = response[10] or None
306
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
308
remote_repo.dont_leave_lock_in_place()
310
remote_repo.lock_write()
311
policy = _mod_bzrdir.UseExistingRepository(remote_repo,
312
final_stack, final_stack_pwd, require_stacking)
313
policy.acquire_repository()
317
bzrdir._format.set_branch_format(self.get_branch_format())
319
# The repo has already been created, but we need to make sure that
320
# we'll make a stackable branch.
321
bzrdir._format.require_stacking(_skip_repo=True)
322
return remote_repo, bzrdir, require_stacking, policy
324
def _open(self, transport):
325
return RemoteBzrDir(transport, self)
327
def __eq__(self, other):
328
if not isinstance(other, RemoteBzrDirFormat):
330
return self.get_format_description() == other.get_format_description()
332
def __return_repository_format(self):
333
# Always return a RemoteRepositoryFormat object, but if a specific bzr
334
# repository format has been asked for, tell the RemoteRepositoryFormat
335
# that it should use that for init() etc.
336
result = RemoteRepositoryFormat()
337
custom_format = getattr(self, '_repository_format', None)
339
if isinstance(custom_format, RemoteRepositoryFormat):
342
# We will use the custom format to create repositories over the
343
# wire; expose its details like rich_root_data for code to
345
result._custom_format = custom_format
348
def get_branch_format(self):
349
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
350
if not isinstance(result, RemoteBranchFormat):
351
new_result = RemoteBranchFormat()
352
new_result._custom_format = result
354
self.set_branch_format(new_result)
358
repository_format = property(__return_repository_format,
359
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
362
class RemoteControlStore(_mod_config.IniFileStore):
363
"""Control store which attempts to use HPSS calls to retrieve control store.
365
Note that this is specific to bzr-based formats.
368
def __init__(self, bzrdir):
369
super(RemoteControlStore, self).__init__()
370
self.controldir = bzrdir
371
self._real_store = None
373
def lock_write(self, token=None):
375
return self._real_store.lock_write(token)
379
return self._real_store.unlock()
382
with self.lock_write():
383
# We need to be able to override the undecorated implementation
384
self.save_without_locking()
386
def save_without_locking(self):
387
super(RemoteControlStore, self).save()
389
def _ensure_real(self):
390
self.controldir._ensure_real()
391
if self._real_store is None:
392
self._real_store = _mod_config.ControlStore(self.controldir)
394
def external_url(self):
395
return urlutils.join(self.branch.user_url, 'control.conf')
397
def _load_content(self):
398
medium = self.controldir._client._medium
399
path = self.controldir._path_for_remote_call(self.controldir._client)
401
response, handler = self.controldir._call_expecting_body(
402
'BzrDir.get_config_file', path)
403
except errors.UnknownSmartMethod:
405
return self._real_store._load_content()
406
if len(response) and response[0] != 'ok':
407
raise errors.UnexpectedSmartServerResponse(response)
408
return handler.read_body_bytes()
410
def _save_content(self, content):
411
# FIXME JRV 2011-11-22: Ideally this should use a
412
# HPSS call too, but at the moment it is not possible
413
# to write lock control directories.
415
return self._real_store._save_content(content)
418
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
92
419
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
421
def __init__(self, transport, format, _client=None, _force_probe=False):
266
664
def destroy_branch(self, name=None):
267
665
"""See BzrDir.destroy_branch"""
269
self._real_bzrdir.destroy_branch(name=name)
667
name = self._get_selected_branch()
669
raise errors.NoColocatedBranchSupport(self)
670
path = self._path_for_remote_call(self._client)
676
response = self._call('BzrDir.destroy_branch', path, *args)
677
except errors.UnknownSmartMethod:
679
self._real_bzrdir.destroy_branch(name=name)
680
self._next_open_branch_result = None
270
682
self._next_open_branch_result = None
683
if response[0] != 'ok':
684
raise SmartProtocolError('unexpected response code %s' % (response,))
272
def create_workingtree(self, revision_id=None, from_branch=None):
686
def create_workingtree(self, revision_id=None, from_branch=None,
687
accelerator_tree=None, hardlink=False):
273
688
raise errors.NotLocalUrl(self.transport.base)
275
def find_branch_format(self):
690
def find_branch_format(self, name=None):
276
691
"""Find the branch 'format' for this bzrdir.
278
693
This might be a synthetic object for e.g. RemoteBranch and SVN.
280
b = self.open_branch()
695
b = self.open_branch(name=name)
283
def get_branch_reference(self):
698
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
699
path = self._path_for_remote_call(self._client)
701
response, handler = self._call_expecting_body(
702
'BzrDir.get_branches', path)
703
except errors.UnknownSmartMethod:
705
return self._real_bzrdir.get_branches()
706
if response[0] != "success":
707
raise errors.UnexpectedSmartServerResponse(response)
708
body = bencode.bdecode(handler.read_body_bytes())
710
for name, value in viewitems(body):
711
ret[name] = self._open_branch(name, value[0], value[1],
712
possible_transports=possible_transports,
713
ignore_fallbacks=ignore_fallbacks)
716
def set_branch_reference(self, target_branch, name=None):
717
"""See BzrDir.set_branch_reference()."""
719
name = self._get_selected_branch()
721
raise errors.NoColocatedBranchSupport(self)
723
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
725
def get_branch_reference(self, name=None):
284
726
"""See BzrDir.get_branch_reference()."""
728
name = self._get_selected_branch()
730
raise errors.NoColocatedBranchSupport(self)
285
731
response = self._get_branch_reference()
286
732
if response[0] == 'ref':
287
733
return response[1]
318
764
raise errors.UnexpectedSmartServerResponse(response)
321
def _get_tree_branch(self):
767
def _get_tree_branch(self, name=None):
322
768
"""See BzrDir._get_tree_branch()."""
323
return None, self.open_branch()
769
return None, self.open_branch(name=name)
325
def open_branch(self, name=None, unsupported=False,
326
ignore_fallbacks=False):
328
raise NotImplementedError('unsupported flag support not implemented yet.')
329
if self._next_open_branch_result is not None:
330
# See create_branch for details.
331
result = self._next_open_branch_result
332
self._next_open_branch_result = None
334
response = self._get_branch_reference()
335
if response[0] == 'ref':
771
def _open_branch(self, name, kind, location_or_format,
772
ignore_fallbacks=False, possible_transports=None):
336
774
# a branch reference, use the existing BranchReference logic.
337
775
format = BranchReferenceFormat()
338
776
return format.open(self, name=name, _found=True,
339
location=response[1], ignore_fallbacks=ignore_fallbacks)
340
branch_format_name = response[1]
777
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
778
possible_transports=possible_transports)
779
branch_format_name = location_or_format
341
780
if not branch_format_name:
342
781
branch_format_name = None
343
782
format = RemoteBranchFormat(network_name=branch_format_name)
344
783
return RemoteBranch(self, self.find_repository(), format=format,
345
setup_stacking=not ignore_fallbacks, name=name)
784
setup_stacking=not ignore_fallbacks, name=name,
785
possible_transports=possible_transports)
787
def open_branch(self, name=None, unsupported=False,
788
ignore_fallbacks=False, possible_transports=None):
790
name = self._get_selected_branch()
792
raise errors.NoColocatedBranchSupport(self)
794
raise NotImplementedError('unsupported flag support not implemented yet.')
795
if self._next_open_branch_result is not None:
796
# See create_branch for details.
797
result = self._next_open_branch_result
798
self._next_open_branch_result = None
800
response = self._get_branch_reference()
801
return self._open_branch(name, response[0], response[1],
802
possible_transports=possible_transports,
803
ignore_fallbacks=ignore_fallbacks)
347
805
def _open_repo_v1(self, path):
348
806
verb = 'BzrDir.find_repository'
529
1012
self._custom_format.supports_tree_reference
530
1013
return self._supports_tree_reference
532
def _vfs_initialize(self, a_bzrdir, shared):
1016
def revision_graph_can_have_wrong_parents(self):
1017
if self._revision_graph_can_have_wrong_parents is None:
1019
self._revision_graph_can_have_wrong_parents = \
1020
self._custom_format.revision_graph_can_have_wrong_parents
1021
return self._revision_graph_can_have_wrong_parents
1023
def _vfs_initialize(self, a_controldir, shared):
533
1024
"""Helper for common code in initialize."""
534
1025
if self._custom_format:
535
1026
# Custom format requested
536
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1027
result = self._custom_format.initialize(a_controldir, shared=shared)
537
1028
elif self._creating_bzrdir is not None:
538
1029
# Use the format that the repository we were created to back
540
1031
prior_repo = self._creating_bzrdir.open_repository()
541
1032
prior_repo._ensure_real()
542
1033
result = prior_repo._real_repository._format.initialize(
543
a_bzrdir, shared=shared)
1034
a_controldir, shared=shared)
545
1036
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
1037
# support remote initialization.
547
1038
# We delegate to a real object at this point (as RemoteBzrDir
548
1039
# delegate to the repository format which would lead to infinite
549
# recursion if we just called a_bzrdir.create_repository.
550
a_bzrdir._ensure_real()
551
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1040
# recursion if we just called a_controldir.create_repository.
1041
a_controldir._ensure_real()
1042
result = a_controldir._real_bzrdir.create_repository(shared=shared)
552
1043
if not isinstance(result, RemoteRepository):
553
return self.open(a_bzrdir)
1044
return self.open(a_controldir)
557
def initialize(self, a_bzrdir, shared=False):
1048
def initialize(self, a_controldir, shared=False):
558
1049
# Being asked to create on a non RemoteBzrDir:
559
if not isinstance(a_bzrdir, RemoteBzrDir):
560
return self._vfs_initialize(a_bzrdir, shared)
561
medium = a_bzrdir._client._medium
1050
if not isinstance(a_controldir, RemoteBzrDir):
1051
return self._vfs_initialize(a_controldir, shared)
1052
medium = a_controldir._client._medium
562
1053
if medium._is_remote_before((1, 13)):
563
return self._vfs_initialize(a_bzrdir, shared)
1054
return self._vfs_initialize(a_controldir, shared)
564
1055
# Creating on a remote bzr dir.
565
1056
# 1) get the network name to use.
566
1057
if self._custom_format:
568
1059
elif self._network_name:
569
1060
network_name = self._network_name
571
# Select the current bzrlib default and ask for that.
572
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1062
# Select the current breezy default and ask for that.
1063
reference_bzrdir_format = controldir.format_registry.get('default')()
573
1064
reference_format = reference_bzrdir_format.repository_format
574
1065
network_name = reference_format.network_name()
575
1066
# 2) try direct creation via RPC
576
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1067
path = a_controldir._path_for_remote_call(a_controldir._client)
577
1068
verb = 'BzrDir.create_repository'
579
1070
shared_str = 'True'
581
1072
shared_str = 'False'
583
response = a_bzrdir._call(verb, path, network_name, shared_str)
1074
response = a_controldir._call(verb, path, network_name, shared_str)
584
1075
except errors.UnknownSmartMethod:
585
1076
# Fallback - use vfs methods
586
1077
medium._remember_remote_is_before((1, 13))
587
return self._vfs_initialize(a_bzrdir, shared)
1078
return self._vfs_initialize(a_controldir, shared)
589
1080
# Turn the response into a RemoteRepository object.
590
1081
format = response_tuple_to_repo_format(response[1:])
591
1082
# Used to support creating a real format instance when needed.
592
format._creating_bzrdir = a_bzrdir
593
remote_repo = RemoteRepository(a_bzrdir, format)
1083
format._creating_bzrdir = a_controldir
1084
remote_repo = RemoteRepository(a_controldir, format)
594
1085
format._creating_repo = remote_repo
595
1086
return remote_repo
597
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
600
return a_bzrdir.open_repository()
1088
def open(self, a_controldir):
1089
if not isinstance(a_controldir, RemoteBzrDir):
1090
raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
1091
return a_controldir.open_repository()
602
1093
def _ensure_real(self):
603
1094
if self._custom_format is None:
604
self._custom_format = repository.network_format_registry.get(
1096
self._custom_format = _mod_repository.network_format_registry.get(
1099
raise errors.UnknownFormatError(kind='repository',
1100
format=self._network_name)
608
1103
def _fetch_order(self):
1195
1777
raise errors.UnexpectedSmartServerResponse(response)
1197
1779
def sprout(self, to_bzrdir, revision_id=None):
1198
# TODO: Option to control what format is created?
1200
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1202
dest_repo.fetch(self, revision_id=revision_id)
1780
"""Create a descendent repository for new development.
1782
Unlike clone, this does not copy the settings of the repository.
1784
with self.lock_read():
1785
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1786
dest_repo.fetch(self, revision_id=revision_id)
1789
def _create_sprouting_repo(self, a_controldir, shared):
1790
if not isinstance(a_controldir._format, self.controldir._format.__class__):
1791
# use target default format.
1792
dest_repo = a_controldir.create_repository()
1794
# Most control formats need the repository to be specifically
1795
# created, but on some old all-in-one formats it's not needed
1797
dest_repo = self._format.initialize(a_controldir, shared=shared)
1798
except errors.UninitializableFormat:
1799
dest_repo = a_controldir.open_repository()
1203
1800
return dest_repo
1205
1802
### These methods are just thin shims to the VFS object for now.
1207
1804
def revision_tree(self, revision_id):
1209
return self._real_repository.revision_tree(revision_id)
1805
with self.lock_read():
1806
revision_id = _mod_revision.ensure_null(revision_id)
1807
if revision_id == _mod_revision.NULL_REVISION:
1808
return InventoryRevisionTree(self,
1809
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1811
return list(self.revision_trees([revision_id]))[0]
1211
1813
def get_serializer_format(self):
1213
return self._real_repository.get_serializer_format()
1814
path = self.controldir._path_for_remote_call(self._client)
1816
response = self._call('VersionedFileRepository.get_serializer_format',
1818
except errors.UnknownSmartMethod:
1820
return self._real_repository.get_serializer_format()
1821
if response[0] != 'ok':
1822
raise errors.UnexpectedSmartServerResponse(response)
1215
1825
def get_commit_builder(self, branch, parents, config, timestamp=None,
1216
1826
timezone=None, committer=None, revprops=None,
1218
# FIXME: It ought to be possible to call this without immediately
1219
# triggering _ensure_real. For now it's the easiest thing to do.
1221
real_repo = self._real_repository
1222
builder = real_repo.get_commit_builder(branch, parents,
1223
config, timestamp=timestamp, timezone=timezone,
1224
committer=committer, revprops=revprops, revision_id=revision_id)
1827
revision_id=None, lossy=False):
1828
"""Obtain a CommitBuilder for this repository.
1830
:param branch: Branch to commit to.
1831
:param parents: Revision ids of the parents of the new revision.
1832
:param config: Configuration to use.
1833
:param timestamp: Optional timestamp recorded for commit.
1834
:param timezone: Optional timezone for timestamp.
1835
:param committer: Optional committer to set for commit.
1836
:param revprops: Optional dictionary of revision properties.
1837
:param revision_id: Optional revision id.
1838
:param lossy: Whether to discard data that can not be natively
1839
represented, when pushing to a foreign VCS
1841
if self._fallback_repositories and not self._format.supports_chks:
1842
raise errors.BzrError("Cannot commit directly to a stacked branch"
1843
" in pre-2a formats. See "
1844
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1845
if self._format.rich_root_data:
1846
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1848
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1849
result = commit_builder_kls(self, parents, config,
1850
timestamp, timezone, committer, revprops, revision_id,
1852
self.start_write_group()
1227
1855
def add_fallback_repository(self, repository):
1228
1856
"""Add a repository to use for looking up data not held locally.
1272
1901
delta, new_revision_id, parents, basis_inv=basis_inv,
1273
1902
propagate_caches=propagate_caches)
1275
def add_revision(self, rev_id, rev, inv=None, config=None):
1277
return self._real_repository.add_revision(
1278
rev_id, rev, inv=inv, config=config)
1904
def add_revision(self, revision_id, rev, inv=None):
1905
_mod_revision.check_not_reserved_id(revision_id)
1906
key = (revision_id,)
1907
# check inventory present
1908
if not self.inventories.get_parent_map([key]):
1910
raise errors.WeaveRevisionNotPresent(revision_id,
1913
# yes, this is not suitable for adding with ghosts.
1914
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1917
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1918
self._add_revision(rev)
1920
def _add_revision(self, rev):
1921
if self._real_repository is not None:
1922
return self._real_repository._add_revision(rev)
1923
text = self._serializer.write_revision_to_string(rev)
1924
key = (rev.revision_id,)
1925
parents = tuple((parent,) for parent in rev.parent_ids)
1926
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1927
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1928
self._format, self._write_group_tokens)
1281
1930
def get_inventory(self, revision_id):
1931
with self.lock_read():
1932
return list(self.iter_inventories([revision_id]))[0]
1934
def _iter_inventories_rpc(self, revision_ids, ordering):
1935
if ordering is None:
1936
ordering = 'unordered'
1937
path = self.controldir._path_for_remote_call(self._client)
1938
body = "\n".join(revision_ids)
1939
response_tuple, response_handler = (
1940
self._call_with_body_bytes_expecting_body(
1941
"VersionedFileRepository.get_inventories",
1942
(path, ordering), body))
1943
if response_tuple[0] != "ok":
1944
raise errors.UnexpectedSmartServerResponse(response_tuple)
1945
deserializer = inventory_delta.InventoryDeltaDeserializer()
1946
byte_stream = response_handler.read_streamed_body()
1947
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1949
# no results whatsoever
1951
src_format, stream = decoded
1952
if src_format.network_name() != self._format.network_name():
1953
raise AssertionError(
1954
"Mismatched RemoteRepository and stream src %r, %r" % (
1955
src_format.network_name(), self._format.network_name()))
1956
# ignore the src format, it's not really relevant
1957
prev_inv = Inventory(root_id=None,
1958
revision_id=_mod_revision.NULL_REVISION)
1959
# there should be just one substream, with inventory deltas
1960
substream_kind, substream = next(stream)
1961
if substream_kind != "inventory-deltas":
1962
raise AssertionError(
1963
"Unexpected stream %r received" % substream_kind)
1964
for record in substream:
1965
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1966
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1967
if parent_id != prev_inv.revision_id:
1968
raise AssertionError("invalid base %r != %r" % (parent_id,
1969
prev_inv.revision_id))
1970
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1971
yield inv, inv.revision_id
1974
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1282
1975
self._ensure_real()
1283
return self._real_repository.get_inventory(revision_id)
1976
return self._real_repository._iter_inventories(revision_ids, ordering)
1285
1978
def iter_inventories(self, revision_ids, ordering=None):
1287
return self._real_repository.iter_inventories(revision_ids, ordering)
1979
"""Get many inventories by revision_ids.
1981
This will buffer some or all of the texts used in constructing the
1982
inventories in memory, but will only parse a single inventory at a
1985
:param revision_ids: The expected revision ids of the inventories.
1986
:param ordering: optional ordering, e.g. 'topological'. If not
1987
specified, the order of revision_ids will be preserved (by
1988
buffering if necessary).
1989
:return: An iterator of inventories.
1991
if ((None in revision_ids)
1992
or (_mod_revision.NULL_REVISION in revision_ids)):
1993
raise ValueError('cannot get null revision inventory')
1994
for inv, revid in self._iter_inventories(revision_ids, ordering):
1996
raise errors.NoSuchRevision(self, revid)
1999
def _iter_inventories(self, revision_ids, ordering=None):
2000
if len(revision_ids) == 0:
2002
missing = set(revision_ids)
2003
if ordering is None:
2004
order_as_requested = True
2006
order = list(revision_ids)
2008
next_revid = order.pop()
2010
order_as_requested = False
2011
if ordering != 'unordered' and self._fallback_repositories:
2012
raise ValueError('unsupported ordering %r' % ordering)
2013
iter_inv_fns = [self._iter_inventories_rpc] + [
2014
fallback._iter_inventories for fallback in
2015
self._fallback_repositories]
2017
for iter_inv in iter_inv_fns:
2018
request = [revid for revid in revision_ids if revid in missing]
2019
for inv, revid in iter_inv(request, ordering):
2022
missing.remove(inv.revision_id)
2023
if ordering != 'unordered':
2027
if order_as_requested:
2028
# Yield as many results as we can while preserving order.
2029
while next_revid in invs:
2030
inv = invs.pop(next_revid)
2031
yield inv, inv.revision_id
2033
next_revid = order.pop()
2035
# We still want to fully consume the stream, just
2036
# in case it is not actually finished at this point
2039
except errors.UnknownSmartMethod:
2040
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2044
if order_as_requested:
2045
if next_revid is not None:
2046
yield None, next_revid
2049
yield invs.get(revid), revid
2052
yield None, missing.pop()
1290
2054
def get_revision(self, revision_id):
1292
return self._real_repository.get_revision(revision_id)
2055
with self.lock_read():
2056
return self.get_revisions([revision_id])[0]
1294
2058
def get_transaction(self):
1295
2059
self._ensure_real()
1296
2060
return self._real_repository.get_transaction()
1299
def clone(self, a_bzrdir, revision_id=None):
1301
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
2062
def clone(self, a_controldir, revision_id=None):
2063
with self.lock_read():
2064
dest_repo = self._create_sprouting_repo(
2065
a_controldir, shared=self.is_shared())
2066
self.copy_content_into(dest_repo, revision_id)
1303
2069
def make_working_trees(self):
1304
2070
"""See Repository.make_working_trees"""
1306
return self._real_repository.make_working_trees()
2071
path = self.controldir._path_for_remote_call(self._client)
2073
response = self._call('Repository.make_working_trees', path)
2074
except errors.UnknownSmartMethod:
2076
return self._real_repository.make_working_trees()
2077
if response[0] not in ('yes', 'no'):
2078
raise SmartProtocolError('unexpected response code %s' % (response,))
2079
return response[0] == 'yes'
1308
2081
def refresh_data(self):
1309
"""Re-read any data needed to to synchronise with disk.
2082
"""Re-read any data needed to synchronise with disk.
1311
2084
This method is intended to be called after another repository instance
1312
2085
(such as one used by a smart server) has inserted data into the
1313
repository. It may not be called during a write group, but may be
1314
called at any other time.
2086
repository. On all repositories this will work outside of write groups.
2087
Some repository formats (pack and newer for breezy native formats)
2088
support refresh_data inside write groups. If called inside a write
2089
group on a repository that does not support refreshing in a write group
2090
IsInWriteGroupError will be raised.
1316
if self.is_in_write_group():
1317
raise errors.InternalBzrError(
1318
"May not refresh_data while in a write group.")
1319
2092
if self._real_repository is not None:
1320
2093
self._real_repository.refresh_data()
2094
# Refresh the parents cache for this object
2095
self._unstacked_provider.disable_cache()
2096
self._unstacked_provider.enable_cache()
1322
2098
def revision_ids_to_search_result(self, result_set):
1323
2099
"""Convert a set of revision ids to a graph SearchResult."""
1324
2100
result_parents = set()
1325
for parents in self.get_graph().get_parent_map(
1326
result_set).itervalues():
2101
for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
1327
2102
result_parents.update(parents)
1328
2103
included_keys = result_set.intersection(result_parents)
1329
2104
start_keys = result_set.difference(included_keys)
1330
2105
exclude_keys = result_parents.difference(result_set)
1331
result = graph.SearchResult(start_keys, exclude_keys,
2106
result = vf_search.SearchResult(start_keys, exclude_keys,
1332
2107
len(result_set), result_set)
1336
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
2110
def search_missing_revision_ids(self, other,
2111
find_ghosts=True, revision_ids=None, if_present_ids=None,
1337
2113
"""Return the revision ids that other has that this does not.
1339
2115
These are returned in topological order.
1341
2117
revision_id: only return revision ids included by revision_id.
1343
return repository.InterRepository.get(
1344
other, self).search_missing_revision_ids(revision_id, find_ghosts)
2119
with self.lock_read():
2120
inter_repo = _mod_repository.InterRepository.get(other, self)
2121
return inter_repo.search_missing_revision_ids(
2122
find_ghosts=find_ghosts, revision_ids=revision_ids,
2123
if_present_ids=if_present_ids, limit=limit)
1346
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
2125
def fetch(self, source, revision_id=None, find_ghosts=False,
1347
2126
fetch_spec=None):
1348
2127
# No base implementation to use as RemoteRepository is not a subclass
1349
2128
# of Repository; so this is a copy of Repository.fetch().
1388
2166
return self._real_repository._get_versioned_file_checker(
1389
2167
revisions, revision_versions_cache)
2169
def _iter_files_bytes_rpc(self, desired_files, absent):
2170
path = self.controldir._path_for_remote_call(self._client)
2173
for (file_id, revid, identifier) in desired_files:
2174
lines.append("%s\0%s" % (
2175
osutils.safe_file_id(file_id),
2176
osutils.safe_revision_id(revid)))
2177
identifiers.append(identifier)
2178
(response_tuple, response_handler) = (
2179
self._call_with_body_bytes_expecting_body(
2180
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2181
if response_tuple != ('ok', ):
2182
response_handler.cancel_read_body()
2183
raise errors.UnexpectedSmartServerResponse(response_tuple)
2184
byte_stream = response_handler.read_streamed_body()
2185
def decompress_stream(start, byte_stream, unused):
2186
decompressor = zlib.decompressobj()
2187
yield decompressor.decompress(start)
2188
while decompressor.unused_data == "":
2190
data = next(byte_stream)
2191
except StopIteration:
2193
yield decompressor.decompress(data)
2194
yield decompressor.flush()
2195
unused.append(decompressor.unused_data)
2198
while not "\n" in unused:
2199
unused += next(byte_stream)
2200
header, rest = unused.split("\n", 1)
2201
args = header.split("\0")
2202
if args[0] == "absent":
2203
absent[identifiers[int(args[3])]] = (args[1], args[2])
2206
elif args[0] == "ok":
2209
raise errors.UnexpectedSmartServerResponse(args)
2211
yield (identifiers[idx],
2212
decompress_stream(rest, byte_stream, unused_chunks))
2213
unused = "".join(unused_chunks)
1391
2215
def iter_files_bytes(self, desired_files):
1392
2216
"""See Repository.iter_file_bytes.
1395
return self._real_repository.iter_files_bytes(desired_files)
2220
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2221
desired_files, absent):
2222
yield identifier, bytes_iterator
2223
for fallback in self._fallback_repositories:
2226
desired_files = [(key[0], key[1], identifier)
2227
for identifier, key in viewitems(absent)]
2228
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2229
del absent[identifier]
2230
yield identifier, bytes_iterator
2232
# There may be more missing items, but raise an exception
2234
missing_identifier = next(iter(absent))
2235
missing_key = absent[missing_identifier]
2236
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2237
file_id=missing_key[0])
2238
except errors.UnknownSmartMethod:
2240
for (identifier, bytes_iterator) in (
2241
self._real_repository.iter_files_bytes(desired_files)):
2242
yield identifier, bytes_iterator
2244
def get_cached_parent_map(self, revision_ids):
2245
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2246
return self._unstacked_provider.get_cached_parent_map(revision_ids)
1397
2248
def get_parent_map(self, revision_ids):
1398
"""See bzrlib.Graph.get_parent_map()."""
2249
"""See breezy.Graph.get_parent_map()."""
1399
2250
return self._make_parents_provider().get_parent_map(revision_ids)
1401
2252
def _get_parent_map_rpc(self, keys):
1529
2369
revision_graph[d[0]] = (NULL_REVISION,)
1530
2370
return revision_graph
1533
2372
def get_signature_text(self, revision_id):
1535
return self._real_repository.get_signature_text(revision_id)
2373
with self.lock_read():
2374
path = self.controldir._path_for_remote_call(self._client)
2376
response_tuple, response_handler = self._call_expecting_body(
2377
'Repository.get_revision_signature_text', path, revision_id)
2378
except errors.UnknownSmartMethod:
2380
return self._real_repository.get_signature_text(revision_id)
2381
except errors.NoSuchRevision as err:
2382
for fallback in self._fallback_repositories:
2384
return fallback.get_signature_text(revision_id)
2385
except errors.NoSuchRevision:
2389
if response_tuple[0] != 'ok':
2390
raise errors.UnexpectedSmartServerResponse(response_tuple)
2391
return response_handler.read_body_bytes()
1538
2393
def _get_inventory_xml(self, revision_id):
1540
return self._real_repository._get_inventory_xml(revision_id)
2394
with self.lock_read():
2395
# This call is used by older working tree formats,
2396
# which stored a serialized basis inventory.
2398
return self._real_repository._get_inventory_xml(revision_id)
1542
2400
def reconcile(self, other=None, thorough=False):
1544
return self._real_repository.reconcile(other=other, thorough=thorough)
2401
from ..reconcile import RepoReconciler
2402
with self.lock_write():
2403
path = self.controldir._path_for_remote_call(self._client)
2405
response, handler = self._call_expecting_body(
2406
'Repository.reconcile', path, self._lock_token)
2407
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2409
return self._real_repository.reconcile(other=other, thorough=thorough)
2410
if response != ('ok', ):
2411
raise errors.UnexpectedSmartServerResponse(response)
2412
body = handler.read_body_bytes()
2413
result = RepoReconciler(self)
2414
for line in body.split('\n'):
2417
key, val_text = line.split(':')
2418
if key == "garbage_inventories":
2419
result.garbage_inventories = int(val_text)
2420
elif key == "inconsistent_parents":
2421
result.inconsistent_parents = int(val_text)
2423
mutter("unknown reconcile key %r" % key)
1546
2426
def all_revision_ids(self):
1548
return self._real_repository.all_revision_ids()
2427
path = self.controldir._path_for_remote_call(self._client)
2429
response_tuple, response_handler = self._call_expecting_body(
2430
"Repository.all_revision_ids", path)
2431
except errors.UnknownSmartMethod:
2433
return self._real_repository.all_revision_ids()
2434
if response_tuple != ("ok", ):
2435
raise errors.UnexpectedSmartServerResponse(response_tuple)
2436
revids = set(response_handler.read_body_bytes().splitlines())
2437
for fallback in self._fallback_repositories:
2438
revids.update(set(fallback.all_revision_ids()))
2441
def _filtered_revision_trees(self, revision_ids, file_ids):
2442
"""Return Tree for a revision on this branch with only some files.
2444
:param revision_ids: a sequence of revision-ids;
2445
a revision-id may not be None or 'null:'
2446
:param file_ids: if not None, the result is filtered
2447
so that only those file-ids, their parents and their
2448
children are included.
2450
inventories = self.iter_inventories(revision_ids)
2451
for inv in inventories:
2452
# Should we introduce a FilteredRevisionTree class rather
2453
# than pre-filter the inventory here?
2454
filtered_inv = inv.filter(file_ids)
2455
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
1551
2457
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1553
return self._real_repository.get_deltas_for_revisions(revisions,
1554
specific_fileids=specific_fileids)
2458
with self.lock_read():
2459
medium = self._client._medium
2460
if medium._is_remote_before((1, 2)):
2462
for delta in self._real_repository.get_deltas_for_revisions(
2463
revisions, specific_fileids):
2466
# Get the revision-ids of interest
2467
required_trees = set()
2468
for revision in revisions:
2469
required_trees.add(revision.revision_id)
2470
required_trees.update(revision.parent_ids[:1])
2472
# Get the matching filtered trees. Note that it's more
2473
# efficient to pass filtered trees to changes_from() rather
2474
# than doing the filtering afterwards. changes_from() could
2475
# arguably do the filtering itself but it's path-based, not
2476
# file-id based, so filtering before or afterwards is
2478
if specific_fileids is None:
2479
trees = dict((t.get_revision_id(), t) for
2480
t in self.revision_trees(required_trees))
2482
trees = dict((t.get_revision_id(), t) for
2483
t in self._filtered_revision_trees(required_trees,
2486
# Calculate the deltas
2487
for revision in revisions:
2488
if not revision.parent_ids:
2489
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2491
old_tree = trees[revision.parent_ids[0]]
2492
yield trees[revision.revision_id].changes_from(old_tree)
1557
2494
def get_revision_delta(self, revision_id, specific_fileids=None):
1559
return self._real_repository.get_revision_delta(revision_id,
1560
specific_fileids=specific_fileids)
2495
with self.lock_read():
2496
r = self.get_revision(revision_id)
2497
return list(self.get_deltas_for_revisions([r],
2498
specific_fileids=specific_fileids))[0]
1563
2500
def revision_trees(self, revision_ids):
1565
return self._real_repository.revision_trees(revision_ids)
2501
with self.lock_read():
2502
inventories = self.iter_inventories(revision_ids)
2503
for inv in inventories:
2504
yield InventoryRevisionTree(self, inv, inv.revision_id)
1568
2506
def get_revision_reconcile(self, revision_id):
1570
return self._real_repository.get_revision_reconcile(revision_id)
2507
with self.lock_read():
2509
return self._real_repository.get_revision_reconcile(revision_id)
1573
2511
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1575
return self._real_repository.check(revision_ids=revision_ids,
1576
callback_refs=callback_refs, check_repo=check_repo)
2512
with self.lock_read():
2514
return self._real_repository.check(revision_ids=revision_ids,
2515
callback_refs=callback_refs, check_repo=check_repo)
1578
2517
def copy_content_into(self, destination, revision_id=None):
1580
return self._real_repository.copy_content_into(
1581
destination, revision_id=revision_id)
2518
"""Make a complete copy of the content in self into destination.
2520
This is a destructive operation! Do not use it on existing
2523
interrepo = _mod_repository.InterRepository.get(self, destination)
2524
return interrepo.copy_content(revision_id)
1583
2526
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1584
2527
# get a tarball of the remote repository, and copy from that into the
1586
from bzrlib import osutils
1588
2530
# TODO: Maybe a progress bar while streaming the tarball?
1589
note("Copying repository content as tarball...")
2531
note(gettext("Copying repository content as tarball..."))
1590
2532
tar_file = self._get_tarball('bz2')
1591
2533
if tar_file is None:
1680
2632
self._ensure_real()
1681
2633
return self._real_repository.texts
1684
def get_revisions(self, revision_ids):
1686
return self._real_repository.get_revisions(revision_ids)
2635
def _iter_revisions_rpc(self, revision_ids):
2636
body = "\n".join(revision_ids)
2637
path = self.controldir._path_for_remote_call(self._client)
2638
response_tuple, response_handler = (
2639
self._call_with_body_bytes_expecting_body(
2640
"Repository.iter_revisions", (path, ), body))
2641
if response_tuple[0] != "ok":
2642
raise errors.UnexpectedSmartServerResponse(response_tuple)
2643
serializer_format = response_tuple[1]
2644
serializer = serializer_format_registry.get(serializer_format)
2645
byte_stream = response_handler.read_streamed_body()
2646
decompressor = zlib.decompressobj()
2648
for bytes in byte_stream:
2649
chunks.append(decompressor.decompress(bytes))
2650
if decompressor.unused_data != "":
2651
chunks.append(decompressor.flush())
2652
yield serializer.read_revision_from_string("".join(chunks))
2653
unused = decompressor.unused_data
2654
decompressor = zlib.decompressobj()
2655
chunks = [decompressor.decompress(unused)]
2656
chunks.append(decompressor.flush())
2657
text = "".join(chunks)
2659
yield serializer.read_revision_from_string("".join(chunks))
2661
def iter_revisions(self, revision_ids):
2662
for rev_id in revision_ids:
2663
if not rev_id or not isinstance(rev_id, bytes):
2664
raise errors.InvalidRevisionId(
2665
revision_id=rev_id, branch=self)
2666
with self.lock_read():
2668
missing = set(revision_ids)
2669
for rev in self._iter_revisions_rpc(revision_ids):
2670
missing.remove(rev.revision_id)
2671
yield (rev.revision_id, rev)
2672
for fallback in self._fallback_repositories:
2675
for (revid, rev) in fallback.iter_revisions(missing):
2678
missing.remove(revid)
2679
for revid in missing:
2681
except errors.UnknownSmartMethod:
2683
for entry in self._real_repository.iter_revisions(revision_ids):
1688
2686
def supports_rich_root(self):
1689
2687
return self._format.rich_root_data
1691
def iter_reverse_revision_history(self, revision_id):
1693
return self._real_repository.iter_reverse_revision_history(revision_id)
1696
2690
def _serializer(self):
1697
2691
return self._format._serializer
1699
2693
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1701
return self._real_repository.store_revision_signature(
1702
gpg_strategy, plaintext, revision_id)
2694
with self.lock_write():
2695
signature = gpg_strategy.sign(plaintext)
2696
self.add_signature_text(revision_id, signature)
1704
2698
def add_signature_text(self, revision_id, signature):
1706
return self._real_repository.add_signature_text(revision_id, signature)
2699
if self._real_repository:
2700
# If there is a real repository the write group will
2701
# be in the real repository as well, so use that:
2703
return self._real_repository.add_signature_text(
2704
revision_id, signature)
2705
path = self.controldir._path_for_remote_call(self._client)
2706
response, handler = self._call_with_body_bytes_expecting_body(
2707
'Repository.add_signature_text', (path, self._lock_token,
2708
revision_id) + tuple(self._write_group_tokens), signature)
2709
handler.cancel_read_body()
2711
if response[0] != 'ok':
2712
raise errors.UnexpectedSmartServerResponse(response)
2713
self._write_group_tokens = response[1:]
1708
2715
def has_signature_for_revision_id(self, revision_id):
1710
return self._real_repository.has_signature_for_revision_id(revision_id)
2716
path = self.controldir._path_for_remote_call(self._client)
2718
response = self._call('Repository.has_signature_for_revision_id',
2720
except errors.UnknownSmartMethod:
2722
return self._real_repository.has_signature_for_revision_id(
2724
if response[0] not in ('yes', 'no'):
2725
raise SmartProtocolError('unexpected response code %s' % (response,))
2726
if response[0] == 'yes':
2728
for fallback in self._fallback_repositories:
2729
if fallback.has_signature_for_revision_id(revision_id):
2733
def verify_revision_signature(self, revision_id, gpg_strategy):
2734
with self.lock_read():
2735
if not self.has_signature_for_revision_id(revision_id):
2736
return gpg.SIGNATURE_NOT_SIGNED, None
2737
signature = self.get_signature_text(revision_id)
2739
testament = _mod_testament.Testament.from_revision(self, revision_id)
2740
plaintext = testament.as_short_text()
2742
return gpg_strategy.verify(signature, plaintext)
1712
2744
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1713
2745
self._ensure_real()
1714
2746
return self._real_repository.item_keys_introduced_by(revision_ids,
1715
2747
_files_pb=_files_pb)
1717
def revision_graph_can_have_wrong_parents(self):
1718
# The answer depends on the remote repo format.
1720
return self._real_repository.revision_graph_can_have_wrong_parents()
1722
2749
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1723
2750
self._ensure_real()
1724
2751
return self._real_repository._find_inconsistent_revision_parents(
2059
3097
def network_name(self):
2060
3098
return self._network_name
2062
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2063
return a_bzrdir.open_branch(name=name,
3100
def open(self, a_controldir, name=None, ignore_fallbacks=False):
3101
return a_controldir.open_branch(name=name,
2064
3102
ignore_fallbacks=ignore_fallbacks)
2066
def _vfs_initialize(self, a_bzrdir, name):
3104
def _vfs_initialize(self, a_controldir, name, append_revisions_only,
2067
3106
# Initialisation when using a local bzrdir object, or a non-vfs init
2068
3107
# method is not available on the server.
2069
3108
# self._custom_format is always set - the start of initialize ensures
2071
if isinstance(a_bzrdir, RemoteBzrDir):
2072
a_bzrdir._ensure_real()
2073
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3110
if isinstance(a_controldir, RemoteBzrDir):
3111
a_controldir._ensure_real()
3112
result = self._custom_format.initialize(a_controldir._real_bzrdir,
3113
name=name, append_revisions_only=append_revisions_only,
3114
repository=repository)
2076
3116
# We assume the bzrdir is parameterised; it may not be.
2077
result = self._custom_format.initialize(a_bzrdir, name)
2078
if (isinstance(a_bzrdir, RemoteBzrDir) and
3117
result = self._custom_format.initialize(a_controldir, name=name,
3118
append_revisions_only=append_revisions_only,
3119
repository=repository)
3120
if (isinstance(a_controldir, RemoteBzrDir) and
2079
3121
not isinstance(result, RemoteBranch)):
2080
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3122
result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
2084
def initialize(self, a_bzrdir, name=None):
3126
def initialize(self, a_controldir, name=None, repository=None,
3127
append_revisions_only=None):
3129
name = a_controldir._get_selected_branch()
2085
3130
# 1) get the network name to use.
2086
3131
if self._custom_format:
2087
3132
network_name = self._custom_format.network_name()
2089
# Select the current bzrlib default and ask for that.
2090
reference_bzrdir_format = bzrdir.format_registry.get('default')()
3134
# Select the current breezy default and ask for that.
3135
reference_bzrdir_format = controldir.format_registry.get('default')()
2091
3136
reference_format = reference_bzrdir_format.get_branch_format()
2092
3137
self._custom_format = reference_format
2093
3138
network_name = reference_format.network_name()
2094
3139
# Being asked to create on a non RemoteBzrDir:
2095
if not isinstance(a_bzrdir, RemoteBzrDir):
2096
return self._vfs_initialize(a_bzrdir, name=name)
2097
medium = a_bzrdir._client._medium
3140
if not isinstance(a_controldir, RemoteBzrDir):
3141
return self._vfs_initialize(a_controldir, name=name,
3142
append_revisions_only=append_revisions_only,
3143
repository=repository)
3144
medium = a_controldir._client._medium
2098
3145
if medium._is_remote_before((1, 13)):
2099
return self._vfs_initialize(a_bzrdir, name=name)
3146
return self._vfs_initialize(a_controldir, name=name,
3147
append_revisions_only=append_revisions_only,
3148
repository=repository)
2100
3149
# Creating on a remote bzr dir.
2101
3150
# 2) try direct creation via RPC
2102
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2103
if name is not None:
3151
path = a_controldir._path_for_remote_call(a_controldir._client)
2104
3153
# XXX JRV20100304: Support creating colocated branches
2105
3154
raise errors.NoColocatedBranchSupport(self)
2106
3155
verb = 'BzrDir.create_branch'
2108
response = a_bzrdir._call(verb, path, network_name)
3157
response = a_controldir._call(verb, path, network_name)
2109
3158
except errors.UnknownSmartMethod:
2110
3159
# Fallback - use vfs methods
2111
3160
medium._remember_remote_is_before((1, 13))
2112
return self._vfs_initialize(a_bzrdir, name=name)
3161
return self._vfs_initialize(a_controldir, name=name,
3162
append_revisions_only=append_revisions_only,
3163
repository=repository)
2113
3164
if response[0] != 'ok':
2114
3165
raise errors.UnexpectedSmartServerResponse(response)
2115
3166
# Turn the response into a RemoteRepository object.
2116
3167
format = RemoteBranchFormat(network_name=response[1])
2117
3168
repo_format = response_tuple_to_repo_format(response[3:])
2118
if response[2] == '':
2119
repo_bzrdir = a_bzrdir
3169
repo_path = response[2]
3170
if repository is not None:
3171
remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3172
url_diff = urlutils.relative_url(repository.user_url,
3175
raise AssertionError(
3176
'repository.user_url %r does not match URL from server '
3177
'response (%r + %r)'
3178
% (repository.user_url, a_controldir.user_url, repo_path))
3179
remote_repo = repository
2121
repo_bzrdir = RemoteBzrDir(
2122
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2124
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2125
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3182
repo_bzrdir = a_controldir
3184
repo_bzrdir = RemoteBzrDir(
3185
a_controldir.root_transport.clone(repo_path), a_controldir._format,
3186
a_controldir._client)
3187
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3188
remote_branch = RemoteBranch(a_controldir, remote_repo,
2126
3189
format=format, setup_stacking=False, name=name)
3190
if append_revisions_only:
3191
remote_branch.set_append_revisions_only(append_revisions_only)
2127
3192
# XXX: We know this is a new branch, so it must have revno 0, revid
2128
3193
# NULL_REVISION. Creating the branch locked would make this be unable
2129
3194
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2148
3213
self._ensure_real()
2149
3214
return self._custom_format.supports_set_append_revisions_only()
3216
def _use_default_local_heads_to_fetch(self):
3217
# If the branch format is a metadir format *and* its heads_to_fetch
3218
# implementation is not overridden vs the base class, we can use the
3219
# base class logic rather than use the heads_to_fetch RPC. This is
3220
# usually cheaper in terms of net round trips, as the last-revision and
3221
# tags info fetched is cached and would be fetched anyway.
3223
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3224
branch_class = self._custom_format._branch_class()
3225
heads_to_fetch_impl = branch_class.heads_to_fetch.__func__
3226
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.__func__:
3231
class RemoteBranchStore(_mod_config.IniFileStore):
3232
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3234
Note that this is specific to bzr-based formats.
3237
def __init__(self, branch):
3238
super(RemoteBranchStore, self).__init__()
3239
self.branch = branch
3241
self._real_store = None
3243
def external_url(self):
3244
return urlutils.join(self.branch.user_url, 'branch.conf')
3246
def _load_content(self):
3247
path = self.branch._remote_path()
3249
response, handler = self.branch._call_expecting_body(
3250
'Branch.get_config_file', path)
3251
except errors.UnknownSmartMethod:
3253
return self._real_store._load_content()
3254
if len(response) and response[0] != 'ok':
3255
raise errors.UnexpectedSmartServerResponse(response)
3256
return handler.read_body_bytes()
3258
def _save_content(self, content):
3259
path = self.branch._remote_path()
3261
response, handler = self.branch._call_with_body_bytes_expecting_body(
3262
'Branch.put_config_file', (path,
3263
self.branch._lock_token, self.branch._repo_lock_token),
3265
except errors.UnknownSmartMethod:
3267
return self._real_store._save_content(content)
3268
handler.cancel_read_body()
3269
if response != ('ok', ):
3270
raise errors.UnexpectedSmartServerResponse(response)
3272
def _ensure_real(self):
3273
self.branch._ensure_real()
3274
if self._real_store is None:
3275
self._real_store = _mod_config.BranchStore(self.branch)
2152
3278
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2153
3279
"""Branch stored on a server accessed by HPSS RPC.
2644
3819
self._ensure_real()
2645
3820
return self._real_branch._set_parent_location(url)
2648
3822
def pull(self, source, overwrite=False, stop_revision=None,
2650
self._clear_cached_state_of_remote_branch_only()
2652
return self._real_branch.pull(
2653
source, overwrite=overwrite, stop_revision=stop_revision,
2654
_override_hook_target=self, **kwargs)
2657
def push(self, target, overwrite=False, stop_revision=None):
2659
return self._real_branch.push(
2660
target, overwrite=overwrite, stop_revision=stop_revision,
2661
_override_hook_source_branch=self)
3824
with self.lock_write():
3825
self._clear_cached_state_of_remote_branch_only()
3827
return self._real_branch.pull(
3828
source, overwrite=overwrite, stop_revision=stop_revision,
3829
_override_hook_target=self, **kwargs)
3831
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3832
with self.lock_read():
3834
return self._real_branch.push(
3835
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3836
_override_hook_source_branch=self)
3838
def peek_lock_mode(self):
3839
return self._lock_mode
2663
3841
def is_locked(self):
2664
3842
return self._lock_count >= 1
3844
def revision_id_to_dotted_revno(self, revision_id):
3845
"""Given a revision id, return its dotted revno.
3847
:return: a tuple like (1,) or (400,1,3).
3849
with self.lock_read():
3851
response = self._call('Branch.revision_id_to_revno',
3852
self._remote_path(), revision_id)
3853
except errors.UnknownSmartMethod:
3855
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3856
if response[0] == 'ok':
3857
return tuple([int(x) for x in response[1:]])
3859
raise errors.UnexpectedSmartServerResponse(response)
2667
3861
def revision_id_to_revno(self, revision_id):
2669
return self._real_branch.revision_id_to_revno(revision_id)
3862
"""Given a revision id on the branch mainline, return its revno.
3866
with self.lock_read():
3868
response = self._call('Branch.revision_id_to_revno',
3869
self._remote_path(), revision_id)
3870
except errors.UnknownSmartMethod:
3872
return self._real_branch.revision_id_to_revno(revision_id)
3873
if response[0] == 'ok':
3874
if len(response) == 2:
3875
return int(response[1])
3876
raise NoSuchRevision(self, revision_id)
3878
raise errors.UnexpectedSmartServerResponse(response)
2672
3880
def set_last_revision_info(self, revno, revision_id):
2673
# XXX: These should be returned by the set_last_revision_info verb
2674
old_revno, old_revid = self.last_revision_info()
2675
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2676
revision_id = ensure_null(revision_id)
2678
response = self._call('Branch.set_last_revision_info',
2679
self._remote_path(), self._lock_token, self._repo_lock_token,
2680
str(revno), revision_id)
2681
except errors.UnknownSmartMethod:
2683
self._clear_cached_state_of_remote_branch_only()
2684
self._real_branch.set_last_revision_info(revno, revision_id)
2685
self._last_revision_info_cache = revno, revision_id
2687
if response == ('ok',):
2688
self._clear_cached_state()
2689
self._last_revision_info_cache = revno, revision_id
2690
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2691
# Update the _real_branch's cache too.
2692
if self._real_branch is not None:
2693
cache = self._last_revision_info_cache
2694
self._real_branch._last_revision_info_cache = cache
2696
raise errors.UnexpectedSmartServerResponse(response)
3881
with self.lock_write():
3882
# XXX: These should be returned by the set_last_revision_info verb
3883
old_revno, old_revid = self.last_revision_info()
3884
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3885
if not revision_id or not isinstance(revision_id, bytes):
3886
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3888
response = self._call('Branch.set_last_revision_info',
3889
self._remote_path(), self._lock_token, self._repo_lock_token,
3890
str(revno), revision_id)
3891
except errors.UnknownSmartMethod:
3893
self._clear_cached_state_of_remote_branch_only()
3894
self._real_branch.set_last_revision_info(revno, revision_id)
3895
self._last_revision_info_cache = revno, revision_id
3897
if response == ('ok',):
3898
self._clear_cached_state()
3899
self._last_revision_info_cache = revno, revision_id
3900
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3901
# Update the _real_branch's cache too.
3902
if self._real_branch is not None:
3903
cache = self._last_revision_info_cache
3904
self._real_branch._last_revision_info_cache = cache
3906
raise errors.UnexpectedSmartServerResponse(response)
2699
3908
def generate_revision_history(self, revision_id, last_rev=None,
2700
3909
other_branch=None):
2701
medium = self._client._medium
2702
if not medium._is_remote_before((1, 6)):
2703
# Use a smart method for 1.6 and above servers
2705
self._set_last_revision_descendant(revision_id, other_branch,
2706
allow_diverged=True, allow_overwrite_descendant=True)
2708
except errors.UnknownSmartMethod:
2709
medium._remember_remote_is_before((1, 6))
2710
self._clear_cached_state_of_remote_branch_only()
2711
self.set_revision_history(self._lefthand_history(revision_id,
2712
last_rev=last_rev,other_branch=other_branch))
3910
with self.lock_write():
3911
medium = self._client._medium
3912
if not medium._is_remote_before((1, 6)):
3913
# Use a smart method for 1.6 and above servers
3915
self._set_last_revision_descendant(revision_id, other_branch,
3916
allow_diverged=True, allow_overwrite_descendant=True)
3918
except errors.UnknownSmartMethod:
3919
medium._remember_remote_is_before((1, 6))
3920
self._clear_cached_state_of_remote_branch_only()
3921
graph = self.repository.get_graph()
3922
(last_revno, last_revid) = self.last_revision_info()
3923
known_revision_ids = [
3924
(last_revid, last_revno),
3925
(_mod_revision.NULL_REVISION, 0),
3927
if last_rev is not None:
3928
if not graph.is_ancestor(last_rev, revision_id):
3929
# our previous tip is not merged into stop_revision
3930
raise errors.DivergedBranches(self, other_branch)
3931
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
3932
self.set_last_revision_info(revno, revision_id)
2714
3934
def set_push_location(self, location):
3935
self._set_config_location('push_location', location)
3937
def heads_to_fetch(self):
3938
if self._format._use_default_local_heads_to_fetch():
3939
# We recognise this format, and its heads-to-fetch implementation
3940
# is the default one (tip + tags). In this case it's cheaper to
3941
# just use the default implementation rather than a special RPC as
3942
# the tip and tags data is cached.
3943
return branch.Branch.heads_to_fetch(self)
3944
medium = self._client._medium
3945
if medium._is_remote_before((2, 4)):
3946
return self._vfs_heads_to_fetch()
3948
return self._rpc_heads_to_fetch()
3949
except errors.UnknownSmartMethod:
3950
medium._remember_remote_is_before((2, 4))
3951
return self._vfs_heads_to_fetch()
3953
def _rpc_heads_to_fetch(self):
3954
response = self._call('Branch.heads_to_fetch', self._remote_path())
3955
if len(response) != 2:
3956
raise errors.UnexpectedSmartServerResponse(response)
3957
must_fetch, if_present_fetch = response
3958
return set(must_fetch), set(if_present_fetch)
3960
def _vfs_heads_to_fetch(self):
2715
3961
self._ensure_real()
2716
return self._real_branch.set_push_location(location)
3962
return self._real_branch.heads_to_fetch()
2719
3965
class RemoteConfig(object):
2774
4030
medium = self._branch._client._medium
2775
4031
if medium._is_remote_before((1, 14)):
2776
4032
return self._vfs_set_option(value, name, section)
4033
if isinstance(value, dict):
4034
if medium._is_remote_before((2, 2)):
4035
return self._vfs_set_option(value, name, section)
4036
return self._set_config_option_dict(value, name, section)
4038
return self._set_config_option(value, name, section)
4040
def _set_config_option(self, value, name, section):
2778
4042
path = self._branch._remote_path()
2779
4043
response = self._branch._client.call('Branch.set_config_option',
2780
4044
path, self._branch._lock_token, self._branch._repo_lock_token,
2781
4045
value.encode('utf8'), name, section or '')
2782
4046
except errors.UnknownSmartMethod:
4047
medium = self._branch._client._medium
2783
4048
medium._remember_remote_is_before((1, 14))
2784
4049
return self._vfs_set_option(value, name, section)
2785
4050
if response != ():
2786
4051
raise errors.UnexpectedSmartServerResponse(response)
4053
def _serialize_option_dict(self, option_dict):
4055
for key, value in option_dict.items():
4056
if isinstance(key, unicode):
4057
key = key.encode('utf8')
4058
if isinstance(value, unicode):
4059
value = value.encode('utf8')
4060
utf8_dict[key] = value
4061
return bencode.bencode(utf8_dict)
4063
def _set_config_option_dict(self, value, name, section):
4065
path = self._branch._remote_path()
4066
serialised_dict = self._serialize_option_dict(value)
4067
response = self._branch._client.call(
4068
'Branch.set_config_option_dict',
4069
path, self._branch._lock_token, self._branch._repo_lock_token,
4070
serialised_dict, name, section or '')
4071
except errors.UnknownSmartMethod:
4072
medium = self._branch._client._medium
4073
medium._remember_remote_is_before((2, 2))
4074
return self._vfs_set_option(value, name, section)
4076
raise errors.UnexpectedSmartServerResponse(response)
2788
4078
def _real_object(self):
2789
4079
self._branch._ensure_real()
2790
4080
return self._branch._real_branch
2867
4151
return context['path']
2868
except KeyError, key_err:
4152
except KeyError as key_err:
2870
4154
return err.error_args[0]
2871
except IndexError, idx_err:
4155
except IndexError as idx_err:
2873
4157
'Missing key %r in context %r', key_err.args[0], context)
2876
if err.error_verb == 'IncompatibleRepositories':
2877
raise errors.IncompatibleRepositories(err.error_args[0],
2878
err.error_args[1], err.error_args[2])
2879
elif err.error_verb == 'NoSuchRevision':
2880
raise NoSuchRevision(find('branch'), err.error_args[0])
2881
elif err.error_verb == 'nosuchrevision':
2882
raise NoSuchRevision(find('repository'), err.error_args[0])
2883
elif err.error_verb == 'nobranch':
2884
if len(err.error_args) >= 1:
2885
extra = err.error_args[0]
2888
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2890
elif err.error_verb == 'norepository':
2891
raise errors.NoRepositoryPresent(find('bzrdir'))
2892
elif err.error_verb == 'LockContention':
2893
raise errors.LockContention('(remote lock)')
2894
elif err.error_verb == 'UnlockableTransport':
2895
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2896
elif err.error_verb == 'LockFailed':
2897
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2898
elif err.error_verb == 'TokenMismatch':
2899
raise errors.TokenMismatch(find('token'), '(remote token)')
2900
elif err.error_verb == 'Diverged':
2901
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2902
elif err.error_verb == 'TipChangeRejected':
2903
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2904
elif err.error_verb == 'UnstackableBranchFormat':
2905
raise errors.UnstackableBranchFormat(*err.error_args)
2906
elif err.error_verb == 'UnstackableRepositoryFormat':
2907
raise errors.UnstackableRepositoryFormat(*err.error_args)
2908
elif err.error_verb == 'NotStacked':
2909
raise errors.NotStacked(branch=find('branch'))
2910
elif err.error_verb == 'PermissionDenied':
2912
if len(err.error_args) >= 2:
2913
extra = err.error_args[1]
2916
raise errors.PermissionDenied(path, extra=extra)
2917
elif err.error_verb == 'ReadError':
2919
raise errors.ReadError(path)
2920
elif err.error_verb == 'NoSuchFile':
2922
raise errors.NoSuchFile(path)
2923
elif err.error_verb == 'FileExists':
2924
raise errors.FileExists(err.error_args[0])
2925
elif err.error_verb == 'DirectoryNotEmpty':
2926
raise errors.DirectoryNotEmpty(err.error_args[0])
2927
elif err.error_verb == 'ShortReadvError':
2928
args = err.error_args
2929
raise errors.ShortReadvError(
2930
args[0], int(args[1]), int(args[2]), int(args[3]))
2931
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
4161
translator = error_translators.get(err.error_verb)
4165
raise translator(err, find, get_path)
4167
translator = no_context_error_translators.get(err.error_verb)
4169
raise errors.UnknownErrorFromSmartServer(err)
4171
raise translator(err)
4174
error_translators.register('NoSuchRevision',
4175
lambda err, find, get_path: NoSuchRevision(
4176
find('branch'), err.error_args[0]))
4177
error_translators.register('nosuchrevision',
4178
lambda err, find, get_path: NoSuchRevision(
4179
find('repository'), err.error_args[0]))
4181
def _translate_nobranch_error(err, find, get_path):
4182
if len(err.error_args) >= 1:
4183
extra = err.error_args[0]
4186
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4189
error_translators.register('nobranch', _translate_nobranch_error)
4190
error_translators.register('norepository',
4191
lambda err, find, get_path: errors.NoRepositoryPresent(
4193
error_translators.register('UnlockableTransport',
4194
lambda err, find, get_path: errors.UnlockableTransport(
4195
find('bzrdir').root_transport))
4196
error_translators.register('TokenMismatch',
4197
lambda err, find, get_path: errors.TokenMismatch(
4198
find('token'), '(remote token)'))
4199
error_translators.register('Diverged',
4200
lambda err, find, get_path: errors.DivergedBranches(
4201
find('branch'), find('other_branch')))
4202
error_translators.register('NotStacked',
4203
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4205
def _translate_PermissionDenied(err, find, get_path):
4207
if len(err.error_args) >= 2:
4208
extra = err.error_args[1]
4211
return errors.PermissionDenied(path, extra=extra)
4213
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4214
error_translators.register('ReadError',
4215
lambda err, find, get_path: errors.ReadError(get_path()))
4216
error_translators.register('NoSuchFile',
4217
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4218
error_translators.register('TokenLockingNotSupported',
4219
lambda err, find, get_path: errors.TokenLockingNotSupported(
4220
find('repository')))
4221
error_translators.register('UnsuspendableWriteGroup',
4222
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4223
repository=find('repository')))
4224
error_translators.register('UnresumableWriteGroup',
4225
lambda err, find, get_path: errors.UnresumableWriteGroup(
4226
repository=find('repository'), write_groups=err.error_args[0],
4227
reason=err.error_args[1]))
4228
no_context_error_translators.register('IncompatibleRepositories',
4229
lambda err: errors.IncompatibleRepositories(
4230
err.error_args[0], err.error_args[1], err.error_args[2]))
4231
no_context_error_translators.register('LockContention',
4232
lambda err: errors.LockContention('(remote lock)'))
4233
no_context_error_translators.register('LockFailed',
4234
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4235
no_context_error_translators.register('TipChangeRejected',
4236
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4237
no_context_error_translators.register('UnstackableBranchFormat',
4238
lambda err: branch.UnstackableBranchFormat(*err.error_args))
4239
no_context_error_translators.register('UnstackableRepositoryFormat',
4240
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4241
no_context_error_translators.register('FileExists',
4242
lambda err: errors.FileExists(err.error_args[0]))
4243
no_context_error_translators.register('DirectoryNotEmpty',
4244
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4246
def _translate_short_readv_error(err):
4247
args = err.error_args
4248
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4251
no_context_error_translators.register('ShortReadvError',
4252
_translate_short_readv_error)
4254
def _translate_unicode_error(err):
2932
4255
encoding = str(err.error_args[0]) # encoding must always be a string
2933
4256
val = err.error_args[1]
2934
4257
start = int(err.error_args[2])