1
# Copyright (C) 2006-2010 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
31
repository as _mod_repository,
33
revision as _mod_revision,
37
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
38
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
39
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
40
from bzrlib.errors import (
44
from bzrlib.lockable_files import LockableFiles
45
from bzrlib.smart import client, vfs, repository as smart_repo
46
from bzrlib.revision import ensure_null, NULL_REVISION
47
from bzrlib.repository import RepositoryWriteLockResult
48
from bzrlib.trace import mutter, note, warning
51
class _RpcHelper(object):
52
"""Mixin class that helps with issuing RPCs."""
54
def _call(self, method, *args, **err_context):
56
return self._client.call(method, *args)
57
except errors.ErrorFromSmartServer, err:
58
self._translate_error(err, **err_context)
60
def _call_expecting_body(self, method, *args, **err_context):
62
return self._client.call_expecting_body(method, *args)
63
except errors.ErrorFromSmartServer, err:
64
self._translate_error(err, **err_context)
66
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
68
return self._client.call_with_body_bytes(method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
72
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
75
return self._client.call_with_body_bytes_expecting_body(
76
method, args, body_bytes)
77
except errors.ErrorFromSmartServer, err:
78
self._translate_error(err, **err_context)
81
def response_tuple_to_repo_format(response):
82
"""Convert a response tuple describing a repository format to a format."""
83
format = RemoteRepositoryFormat()
84
format._rich_root_data = (response[0] == 'yes')
85
format._supports_tree_reference = (response[1] == 'yes')
86
format._supports_external_lookups = (response[2] == 'yes')
87
format._network_name = response[3]
91
# Note: RemoteBzrDirFormat is in bzrdir.py
93
class RemoteBzrDir(BzrDir, _RpcHelper):
94
"""Control directory on a remote server, accessed via bzr:// or similar."""
96
def __init__(self, transport, format, _client=None, _force_probe=False):
97
"""Construct a RemoteBzrDir.
99
:param _client: Private parameter for testing. Disables probing and the
100
use of a real bzrdir.
102
BzrDir.__init__(self, transport, format)
103
# this object holds a delegated bzrdir that uses file-level operations
104
# to talk to the other side
105
self._real_bzrdir = None
106
self._has_working_tree = None
107
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
108
# create_branch for details.
109
self._next_open_branch_result = None
112
medium = transport.get_smart_medium()
113
self._client = client._SmartClient(medium)
115
self._client = _client
122
return '%s(%r)' % (self.__class__.__name__, self._client)
124
def _probe_bzrdir(self):
125
medium = self._client._medium
126
path = self._path_for_remote_call(self._client)
127
if medium._is_remote_before((2, 1)):
131
self._rpc_open_2_1(path)
133
except errors.UnknownSmartMethod:
134
medium._remember_remote_is_before((2, 1))
137
def _rpc_open_2_1(self, path):
138
response = self._call('BzrDir.open_2.1', path)
139
if response == ('no',):
140
raise errors.NotBranchError(path=self.root_transport.base)
141
elif response[0] == 'yes':
142
if response[1] == 'yes':
143
self._has_working_tree = True
144
elif response[1] == 'no':
145
self._has_working_tree = False
147
raise errors.UnexpectedSmartServerResponse(response)
149
raise errors.UnexpectedSmartServerResponse(response)
151
def _rpc_open(self, path):
152
response = self._call('BzrDir.open', path)
153
if response not in [('yes',), ('no',)]:
154
raise errors.UnexpectedSmartServerResponse(response)
155
if response == ('no',):
156
raise errors.NotBranchError(path=self.root_transport.base)
158
def _ensure_real(self):
159
"""Ensure that there is a _real_bzrdir set.
161
Used before calls to self._real_bzrdir.
163
if not self._real_bzrdir:
164
if 'hpssvfs' in debug.debug_flags:
166
warning('VFS BzrDir access triggered\n%s',
167
''.join(traceback.format_stack()))
168
self._real_bzrdir = BzrDir.open_from_transport(
169
self.root_transport, _server_formats=False)
170
self._format._network_name = \
171
self._real_bzrdir._format.network_name()
173
def _translate_error(self, err, **context):
174
_translate_error(err, bzrdir=self, **context)
176
def break_lock(self):
177
# Prevent aliasing problems in the next_open_branch_result cache.
178
# See create_branch for rationale.
179
self._next_open_branch_result = None
180
return BzrDir.break_lock(self)
182
def _vfs_cloning_metadir(self, require_stacking=False):
184
return self._real_bzrdir.cloning_metadir(
185
require_stacking=require_stacking)
187
def cloning_metadir(self, require_stacking=False):
188
medium = self._client._medium
189
if medium._is_remote_before((1, 13)):
190
return self._vfs_cloning_metadir(require_stacking=require_stacking)
191
verb = 'BzrDir.cloning_metadir'
196
path = self._path_for_remote_call(self._client)
198
response = self._call(verb, path, stacking)
199
except errors.UnknownSmartMethod:
200
medium._remember_remote_is_before((1, 13))
201
return self._vfs_cloning_metadir(require_stacking=require_stacking)
202
except errors.UnknownErrorFromSmartServer, err:
203
if err.error_tuple != ('BranchReference',):
205
# We need to resolve the branch reference to determine the
206
# cloning_metadir. This causes unnecessary RPCs to open the
207
# referenced branch (and bzrdir, etc) but only when the caller
208
# didn't already resolve the branch reference.
209
referenced_branch = self.open_branch()
210
return referenced_branch.bzrdir.cloning_metadir()
211
if len(response) != 3:
212
raise errors.UnexpectedSmartServerResponse(response)
213
control_name, repo_name, branch_info = response
214
if len(branch_info) != 2:
215
raise errors.UnexpectedSmartServerResponse(response)
216
branch_ref, branch_name = branch_info
217
format = bzrdir.network_format_registry.get(control_name)
219
format.repository_format = repository.network_format_registry.get(
221
if branch_ref == 'ref':
222
# XXX: we need possible_transports here to avoid reopening the
223
# connection to the referenced location
224
ref_bzrdir = BzrDir.open(branch_name)
225
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
226
format.set_branch_format(branch_format)
227
elif branch_ref == 'branch':
229
format.set_branch_format(
230
branch.network_format_registry.get(branch_name))
232
raise errors.UnexpectedSmartServerResponse(response)
235
def create_repository(self, shared=False):
236
# as per meta1 formats - just delegate to the format object which may
238
result = self._format.repository_format.initialize(self, shared)
239
if not isinstance(result, RemoteRepository):
240
return self.open_repository()
244
def destroy_repository(self):
245
"""See BzrDir.destroy_repository"""
247
self._real_bzrdir.destroy_repository()
249
def create_branch(self, name=None):
250
# as per meta1 formats - just delegate to the format object which may
252
real_branch = self._format.get_branch_format().initialize(self,
254
if not isinstance(real_branch, RemoteBranch):
255
result = RemoteBranch(self, self.find_repository(), real_branch,
259
# BzrDir.clone_on_transport() uses the result of create_branch but does
260
# not return it to its callers; we save approximately 8% of our round
261
# trips by handing the branch we created back to the first caller to
262
# open_branch rather than probing anew. Long term we need a API in
263
# bzrdir that doesn't discard result objects (like result_branch).
265
self._next_open_branch_result = result
268
def destroy_branch(self, name=None):
269
"""See BzrDir.destroy_branch"""
271
self._real_bzrdir.destroy_branch(name=name)
272
self._next_open_branch_result = None
274
def create_workingtree(self, revision_id=None, from_branch=None):
275
raise errors.NotLocalUrl(self.transport.base)
277
def find_branch_format(self, name=None):
278
"""Find the branch 'format' for this bzrdir.
280
This might be a synthetic object for e.g. RemoteBranch and SVN.
282
b = self.open_branch(name=name)
285
def get_branch_reference(self, name=None):
286
"""See BzrDir.get_branch_reference()."""
288
# XXX JRV20100304: Support opening colocated branches
289
raise errors.NoColocatedBranchSupport(self)
290
response = self._get_branch_reference()
291
if response[0] == 'ref':
296
def _get_branch_reference(self):
297
path = self._path_for_remote_call(self._client)
298
medium = self._client._medium
300
('BzrDir.open_branchV3', (2, 1)),
301
('BzrDir.open_branchV2', (1, 13)),
302
('BzrDir.open_branch', None),
304
for verb, required_version in candidate_calls:
305
if required_version and medium._is_remote_before(required_version):
308
response = self._call(verb, path)
309
except errors.UnknownSmartMethod:
310
if required_version is None:
312
medium._remember_remote_is_before(required_version)
315
if verb == 'BzrDir.open_branch':
316
if response[0] != 'ok':
317
raise errors.UnexpectedSmartServerResponse(response)
318
if response[1] != '':
319
return ('ref', response[1])
321
return ('branch', '')
322
if response[0] not in ('ref', 'branch'):
323
raise errors.UnexpectedSmartServerResponse(response)
326
def _get_tree_branch(self, name=None):
327
"""See BzrDir._get_tree_branch()."""
328
return None, self.open_branch(name=name)
330
def open_branch(self, name=None, unsupported=False,
331
ignore_fallbacks=False):
333
raise NotImplementedError('unsupported flag support not implemented yet.')
334
if self._next_open_branch_result is not None:
335
# See create_branch for details.
336
result = self._next_open_branch_result
337
self._next_open_branch_result = None
339
response = self._get_branch_reference()
340
if response[0] == 'ref':
341
# a branch reference, use the existing BranchReference logic.
342
format = BranchReferenceFormat()
343
return format.open(self, name=name, _found=True,
344
location=response[1], ignore_fallbacks=ignore_fallbacks)
345
branch_format_name = response[1]
346
if not branch_format_name:
347
branch_format_name = None
348
format = RemoteBranchFormat(network_name=branch_format_name)
349
return RemoteBranch(self, self.find_repository(), format=format,
350
setup_stacking=not ignore_fallbacks, name=name)
352
def _open_repo_v1(self, path):
353
verb = 'BzrDir.find_repository'
354
response = self._call(verb, path)
355
if response[0] != 'ok':
356
raise errors.UnexpectedSmartServerResponse(response)
357
# servers that only support the v1 method don't support external
360
repo = self._real_bzrdir.open_repository()
361
response = response + ('no', repo._format.network_name())
362
return response, repo
364
def _open_repo_v2(self, path):
365
verb = 'BzrDir.find_repositoryV2'
366
response = self._call(verb, path)
367
if response[0] != 'ok':
368
raise errors.UnexpectedSmartServerResponse(response)
370
repo = self._real_bzrdir.open_repository()
371
response = response + (repo._format.network_name(),)
372
return response, repo
374
def _open_repo_v3(self, path):
375
verb = 'BzrDir.find_repositoryV3'
376
medium = self._client._medium
377
if medium._is_remote_before((1, 13)):
378
raise errors.UnknownSmartMethod(verb)
380
response = self._call(verb, path)
381
except errors.UnknownSmartMethod:
382
medium._remember_remote_is_before((1, 13))
384
if response[0] != 'ok':
385
raise errors.UnexpectedSmartServerResponse(response)
386
return response, None
388
def open_repository(self):
389
path = self._path_for_remote_call(self._client)
391
for probe in [self._open_repo_v3, self._open_repo_v2,
394
response, real_repo = probe(path)
396
except errors.UnknownSmartMethod:
399
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
400
if response[0] != 'ok':
401
raise errors.UnexpectedSmartServerResponse(response)
402
if len(response) != 6:
403
raise SmartProtocolError('incorrect response length %s' % (response,))
404
if response[1] == '':
405
# repo is at this dir.
406
format = response_tuple_to_repo_format(response[2:])
407
# Used to support creating a real format instance when needed.
408
format._creating_bzrdir = self
409
remote_repo = RemoteRepository(self, format)
410
format._creating_repo = remote_repo
411
if real_repo is not None:
412
remote_repo._set_real_repository(real_repo)
415
raise errors.NoRepositoryPresent(self)
417
def has_workingtree(self):
418
if self._has_working_tree is None:
420
self._has_working_tree = self._real_bzrdir.has_workingtree()
421
return self._has_working_tree
423
def open_workingtree(self, recommend_upgrade=True):
424
if self.has_workingtree():
425
raise errors.NotLocalUrl(self.root_transport)
427
raise errors.NoWorkingTree(self.root_transport.base)
429
def _path_for_remote_call(self, client):
430
"""Return the path to be used for this bzrdir in a remote call."""
431
return client.remote_path_from_transport(self.root_transport)
433
def get_branch_transport(self, branch_format, name=None):
435
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
437
def get_repository_transport(self, repository_format):
439
return self._real_bzrdir.get_repository_transport(repository_format)
441
def get_workingtree_transport(self, workingtree_format):
443
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
445
def can_convert_format(self):
446
"""Upgrading of remote bzrdirs is not supported yet."""
449
def needs_format_conversion(self, format=None):
450
"""Upgrading of remote bzrdirs is not supported yet."""
452
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
453
% 'needs_format_conversion(format=None)')
456
def clone(self, url, revision_id=None, force_new_repo=False,
457
preserve_stacking=False):
459
return self._real_bzrdir.clone(url, revision_id=revision_id,
460
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
462
def _get_config(self):
463
return RemoteBzrDirConfig(self)
466
class RemoteRepositoryFormat(repository.RepositoryFormat):
467
"""Format for repositories accessed over a _SmartClient.
469
Instances of this repository are represented by RemoteRepository
472
The RemoteRepositoryFormat is parameterized during construction
473
to reflect the capabilities of the real, remote format. Specifically
474
the attributes rich_root_data and supports_tree_reference are set
475
on a per instance basis, and are not set (and should not be) at
478
:ivar _custom_format: If set, a specific concrete repository format that
479
will be used when initializing a repository with this
480
RemoteRepositoryFormat.
481
:ivar _creating_repo: If set, the repository object that this
482
RemoteRepositoryFormat was created for: it can be called into
483
to obtain data like the network name.
486
_matchingbzrdir = RemoteBzrDirFormat()
489
repository.RepositoryFormat.__init__(self)
490
self._custom_format = None
491
self._network_name = None
492
self._creating_bzrdir = None
493
self._supports_chks = None
494
self._supports_external_lookups = None
495
self._supports_tree_reference = None
496
self._rich_root_data = None
499
return "%s(_network_name=%r)" % (self.__class__.__name__,
503
def fast_deltas(self):
505
return self._custom_format.fast_deltas
508
def rich_root_data(self):
509
if self._rich_root_data is None:
511
self._rich_root_data = self._custom_format.rich_root_data
512
return self._rich_root_data
515
def supports_chks(self):
516
if self._supports_chks is None:
518
self._supports_chks = self._custom_format.supports_chks
519
return self._supports_chks
522
def supports_external_lookups(self):
523
if self._supports_external_lookups is None:
525
self._supports_external_lookups = \
526
self._custom_format.supports_external_lookups
527
return self._supports_external_lookups
530
def supports_tree_reference(self):
531
if self._supports_tree_reference is None:
533
self._supports_tree_reference = \
534
self._custom_format.supports_tree_reference
535
return self._supports_tree_reference
537
def _vfs_initialize(self, a_bzrdir, shared):
538
"""Helper for common code in initialize."""
539
if self._custom_format:
540
# Custom format requested
541
result = self._custom_format.initialize(a_bzrdir, shared=shared)
542
elif self._creating_bzrdir is not None:
543
# Use the format that the repository we were created to back
545
prior_repo = self._creating_bzrdir.open_repository()
546
prior_repo._ensure_real()
547
result = prior_repo._real_repository._format.initialize(
548
a_bzrdir, shared=shared)
550
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
551
# support remote initialization.
552
# We delegate to a real object at this point (as RemoteBzrDir
553
# delegate to the repository format which would lead to infinite
554
# recursion if we just called a_bzrdir.create_repository.
555
a_bzrdir._ensure_real()
556
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
557
if not isinstance(result, RemoteRepository):
558
return self.open(a_bzrdir)
562
def initialize(self, a_bzrdir, shared=False):
563
# Being asked to create on a non RemoteBzrDir:
564
if not isinstance(a_bzrdir, RemoteBzrDir):
565
return self._vfs_initialize(a_bzrdir, shared)
566
medium = a_bzrdir._client._medium
567
if medium._is_remote_before((1, 13)):
568
return self._vfs_initialize(a_bzrdir, shared)
569
# Creating on a remote bzr dir.
570
# 1) get the network name to use.
571
if self._custom_format:
572
network_name = self._custom_format.network_name()
573
elif self._network_name:
574
network_name = self._network_name
576
# Select the current bzrlib default and ask for that.
577
reference_bzrdir_format = bzrdir.format_registry.get('default')()
578
reference_format = reference_bzrdir_format.repository_format
579
network_name = reference_format.network_name()
580
# 2) try direct creation via RPC
581
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
582
verb = 'BzrDir.create_repository'
588
response = a_bzrdir._call(verb, path, network_name, shared_str)
589
except errors.UnknownSmartMethod:
590
# Fallback - use vfs methods
591
medium._remember_remote_is_before((1, 13))
592
return self._vfs_initialize(a_bzrdir, shared)
594
# Turn the response into a RemoteRepository object.
595
format = response_tuple_to_repo_format(response[1:])
596
# Used to support creating a real format instance when needed.
597
format._creating_bzrdir = a_bzrdir
598
remote_repo = RemoteRepository(a_bzrdir, format)
599
format._creating_repo = remote_repo
602
def open(self, a_bzrdir):
603
if not isinstance(a_bzrdir, RemoteBzrDir):
604
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
605
return a_bzrdir.open_repository()
607
def _ensure_real(self):
608
if self._custom_format is None:
609
self._custom_format = repository.network_format_registry.get(
613
def _fetch_order(self):
615
return self._custom_format._fetch_order
618
def _fetch_uses_deltas(self):
620
return self._custom_format._fetch_uses_deltas
623
def _fetch_reconcile(self):
625
return self._custom_format._fetch_reconcile
627
def get_format_description(self):
629
return 'Remote: ' + self._custom_format.get_format_description()
631
def __eq__(self, other):
632
return self.__class__ is other.__class__
634
def network_name(self):
635
if self._network_name:
636
return self._network_name
637
self._creating_repo._ensure_real()
638
return self._creating_repo._real_repository._format.network_name()
641
def pack_compresses(self):
643
return self._custom_format.pack_compresses
646
def _serializer(self):
648
return self._custom_format._serializer
651
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
652
controldir.ControlComponent):
653
"""Repository accessed over rpc.
655
For the moment most operations are performed using local transport-backed
659
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
660
"""Create a RemoteRepository instance.
662
:param remote_bzrdir: The bzrdir hosting this repository.
663
:param format: The RemoteFormat object to use.
664
:param real_repository: If not None, a local implementation of the
665
repository logic for the repository, usually accessing the data
667
:param _client: Private testing parameter - override the smart client
668
to be used by the repository.
671
self._real_repository = real_repository
673
self._real_repository = None
674
self.bzrdir = remote_bzrdir
676
self._client = remote_bzrdir._client
678
self._client = _client
679
self._format = format
680
self._lock_mode = None
681
self._lock_token = None
683
self._leave_lock = False
684
# Cache of revision parents; misses are cached during read locks, and
685
# write locks when no _real_repository has been set.
686
self._unstacked_provider = graph.CachingParentsProvider(
687
get_parent_map=self._get_parent_map_rpc)
688
self._unstacked_provider.disable_cache()
690
# These depend on the actual remote format, so force them off for
691
# maximum compatibility. XXX: In future these should depend on the
692
# remote repository instance, but this is irrelevant until we perform
693
# reconcile via an RPC call.
694
self._reconcile_does_inventory_gc = False
695
self._reconcile_fixes_text_parents = False
696
self._reconcile_backsup_inventory = False
697
self.base = self.bzrdir.transport.base
698
# Additional places to query for data.
699
self._fallback_repositories = []
702
def user_transport(self):
703
return self.bzrdir.user_transport
706
def control_transport(self):
707
# XXX: Normally you shouldn't directly get at the remote repository
708
# transport, but I'm not sure it's worth making this method
709
# optional -- mbp 2010-04-21
710
return self.bzrdir.get_repository_transport(None)
713
return "%s(%s)" % (self.__class__.__name__, self.base)
717
def abort_write_group(self, suppress_errors=False):
718
"""Complete a write group on the decorated repository.
720
Smart methods perform operations in a single step so this API
721
is not really applicable except as a compatibility thunk
722
for older plugins that don't use e.g. the CommitBuilder
725
:param suppress_errors: see Repository.abort_write_group.
728
return self._real_repository.abort_write_group(
729
suppress_errors=suppress_errors)
733
"""Decorate the real repository for now.
735
In the long term a full blown network facility is needed to avoid
736
creating a real repository object locally.
739
return self._real_repository.chk_bytes
741
def commit_write_group(self):
742
"""Complete a write group on the decorated repository.
744
Smart methods perform operations in a single step so this API
745
is not really applicable except as a compatibility thunk
746
for older plugins that don't use e.g. the CommitBuilder
750
return self._real_repository.commit_write_group()
752
def resume_write_group(self, tokens):
754
return self._real_repository.resume_write_group(tokens)
756
def suspend_write_group(self):
758
return self._real_repository.suspend_write_group()
760
def get_missing_parent_inventories(self, check_for_missing_texts=True):
762
return self._real_repository.get_missing_parent_inventories(
763
check_for_missing_texts=check_for_missing_texts)
765
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
767
return self._real_repository.get_rev_id_for_revno(
770
def get_rev_id_for_revno(self, revno, known_pair):
771
"""See Repository.get_rev_id_for_revno."""
772
path = self.bzrdir._path_for_remote_call(self._client)
774
if self._client._medium._is_remote_before((1, 17)):
775
return self._get_rev_id_for_revno_vfs(revno, known_pair)
776
response = self._call(
777
'Repository.get_rev_id_for_revno', path, revno, known_pair)
778
except errors.UnknownSmartMethod:
779
self._client._medium._remember_remote_is_before((1, 17))
780
return self._get_rev_id_for_revno_vfs(revno, known_pair)
781
if response[0] == 'ok':
782
return True, response[1]
783
elif response[0] == 'history-incomplete':
784
known_pair = response[1:3]
785
for fallback in self._fallback_repositories:
786
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
791
# Not found in any fallbacks
792
return False, known_pair
794
raise errors.UnexpectedSmartServerResponse(response)
796
def _ensure_real(self):
797
"""Ensure that there is a _real_repository set.
799
Used before calls to self._real_repository.
801
Note that _ensure_real causes many roundtrips to the server which are
802
not desirable, and prevents the use of smart one-roundtrip RPC's to
803
perform complex operations (such as accessing parent data, streaming
804
revisions etc). Adding calls to _ensure_real should only be done when
805
bringing up new functionality, adding fallbacks for smart methods that
806
require a fallback path, and never to replace an existing smart method
807
invocation. If in doubt chat to the bzr network team.
809
if self._real_repository is None:
810
if 'hpssvfs' in debug.debug_flags:
812
warning('VFS Repository access triggered\n%s',
813
''.join(traceback.format_stack()))
814
self._unstacked_provider.missing_keys.clear()
815
self.bzrdir._ensure_real()
816
self._set_real_repository(
817
self.bzrdir._real_bzrdir.open_repository())
819
def _translate_error(self, err, **context):
820
self.bzrdir._translate_error(err, repository=self, **context)
822
def find_text_key_references(self):
823
"""Find the text key references within the repository.
825
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
826
revision_ids. Each altered file-ids has the exact revision_ids that
827
altered it listed explicitly.
828
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
829
to whether they were referred to by the inventory of the
830
revision_id that they contain. The inventory texts from all present
831
revision ids are assessed to generate this report.
834
return self._real_repository.find_text_key_references()
836
def _generate_text_key_index(self):
837
"""Generate a new text key index for the repository.
839
This is an expensive function that will take considerable time to run.
841
:return: A dict mapping (file_id, revision_id) tuples to a list of
842
parents, also (file_id, revision_id) tuples.
845
return self._real_repository._generate_text_key_index()
847
def _get_revision_graph(self, revision_id):
848
"""Private method for using with old (< 1.2) servers to fallback."""
849
if revision_id is None:
851
elif revision.is_null(revision_id):
854
path = self.bzrdir._path_for_remote_call(self._client)
855
response = self._call_expecting_body(
856
'Repository.get_revision_graph', path, revision_id)
857
response_tuple, response_handler = response
858
if response_tuple[0] != 'ok':
859
raise errors.UnexpectedSmartServerResponse(response_tuple)
860
coded = response_handler.read_body_bytes()
862
# no revisions in this repository!
864
lines = coded.split('\n')
867
d = tuple(line.split())
868
revision_graph[d[0]] = d[1:]
870
return revision_graph
873
"""See Repository._get_sink()."""
874
return RemoteStreamSink(self)
876
def _get_source(self, to_format):
877
"""Return a source for streaming from this repository."""
878
return RemoteStreamSource(self, to_format)
881
def has_revision(self, revision_id):
882
"""True if this repository has a copy of the revision."""
883
# Copy of bzrlib.repository.Repository.has_revision
884
return revision_id in self.has_revisions((revision_id,))
887
def has_revisions(self, revision_ids):
888
"""Probe to find out the presence of multiple revisions.
890
:param revision_ids: An iterable of revision_ids.
891
:return: A set of the revision_ids that were present.
893
# Copy of bzrlib.repository.Repository.has_revisions
894
parent_map = self.get_parent_map(revision_ids)
895
result = set(parent_map)
896
if _mod_revision.NULL_REVISION in revision_ids:
897
result.add(_mod_revision.NULL_REVISION)
900
def _has_same_fallbacks(self, other_repo):
901
"""Returns true if the repositories have the same fallbacks."""
902
# XXX: copied from Repository; it should be unified into a base class
903
# <https://bugs.launchpad.net/bzr/+bug/401622>
904
my_fb = self._fallback_repositories
905
other_fb = other_repo._fallback_repositories
906
if len(my_fb) != len(other_fb):
908
for f, g in zip(my_fb, other_fb):
909
if not f.has_same_location(g):
913
def has_same_location(self, other):
914
# TODO: Move to RepositoryBase and unify with the regular Repository
915
# one; unfortunately the tests rely on slightly different behaviour at
916
# present -- mbp 20090710
917
return (self.__class__ is other.__class__ and
918
self.bzrdir.transport.base == other.bzrdir.transport.base)
920
def get_graph(self, other_repository=None):
921
"""Return the graph for this repository format"""
922
parents_provider = self._make_parents_provider(other_repository)
923
return graph.Graph(parents_provider)
926
def get_known_graph_ancestry(self, revision_ids):
927
"""Return the known graph for a set of revision ids and their ancestors.
929
st = static_tuple.StaticTuple
930
revision_keys = [st(r_id).intern() for r_id in revision_ids]
931
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
932
return graph.GraphThunkIdsToKeys(known_graph)
934
def gather_stats(self, revid=None, committers=None):
935
"""See Repository.gather_stats()."""
936
path = self.bzrdir._path_for_remote_call(self._client)
937
# revid can be None to indicate no revisions, not just NULL_REVISION
938
if revid is None or revision.is_null(revid):
942
if committers is None or not committers:
943
fmt_committers = 'no'
945
fmt_committers = 'yes'
946
response_tuple, response_handler = self._call_expecting_body(
947
'Repository.gather_stats', path, fmt_revid, fmt_committers)
948
if response_tuple[0] != 'ok':
949
raise errors.UnexpectedSmartServerResponse(response_tuple)
951
body = response_handler.read_body_bytes()
953
for line in body.split('\n'):
956
key, val_text = line.split(':')
957
if key in ('revisions', 'size', 'committers'):
958
result[key] = int(val_text)
959
elif key in ('firstrev', 'latestrev'):
960
values = val_text.split(' ')[1:]
961
result[key] = (float(values[0]), long(values[1]))
965
def find_branches(self, using=False):
966
"""See Repository.find_branches()."""
967
# should be an API call to the server.
969
return self._real_repository.find_branches(using=using)
971
def get_physical_lock_status(self):
972
"""See Repository.get_physical_lock_status()."""
973
# should be an API call to the server.
975
return self._real_repository.get_physical_lock_status()
977
def is_in_write_group(self):
978
"""Return True if there is an open write group.
980
write groups are only applicable locally for the smart server..
982
if self._real_repository:
983
return self._real_repository.is_in_write_group()
986
return self._lock_count >= 1
989
"""See Repository.is_shared()."""
990
path = self.bzrdir._path_for_remote_call(self._client)
991
response = self._call('Repository.is_shared', path)
992
if response[0] not in ('yes', 'no'):
993
raise SmartProtocolError('unexpected response code %s' % (response,))
994
return response[0] == 'yes'
996
def is_write_locked(self):
997
return self._lock_mode == 'w'
999
def _warn_if_deprecated(self, branch=None):
1000
# If we have a real repository, the check will be done there, if we
1001
# don't the check will be done remotely.
1004
def lock_read(self):
1005
"""Lock the repository for read operations.
1007
:return: A bzrlib.lock.LogicalLockResult.
1009
# wrong eventually - want a local lock cache context
1010
if not self._lock_mode:
1011
self._note_lock('r')
1012
self._lock_mode = 'r'
1013
self._lock_count = 1
1014
self._unstacked_provider.enable_cache(cache_misses=True)
1015
if self._real_repository is not None:
1016
self._real_repository.lock_read()
1017
for repo in self._fallback_repositories:
1020
self._lock_count += 1
1021
return lock.LogicalLockResult(self.unlock)
1023
def _remote_lock_write(self, token):
1024
path = self.bzrdir._path_for_remote_call(self._client)
1027
err_context = {'token': token}
1028
response = self._call('Repository.lock_write', path, token,
1030
if response[0] == 'ok':
1031
ok, token = response
1034
raise errors.UnexpectedSmartServerResponse(response)
1036
def lock_write(self, token=None, _skip_rpc=False):
1037
if not self._lock_mode:
1038
self._note_lock('w')
1040
if self._lock_token is not None:
1041
if token != self._lock_token:
1042
raise errors.TokenMismatch(token, self._lock_token)
1043
self._lock_token = token
1045
self._lock_token = self._remote_lock_write(token)
1046
# if self._lock_token is None, then this is something like packs or
1047
# svn where we don't get to lock the repo, or a weave style repository
1048
# where we cannot lock it over the wire and attempts to do so will
1050
if self._real_repository is not None:
1051
self._real_repository.lock_write(token=self._lock_token)
1052
if token is not None:
1053
self._leave_lock = True
1055
self._leave_lock = False
1056
self._lock_mode = 'w'
1057
self._lock_count = 1
1058
cache_misses = self._real_repository is None
1059
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1060
for repo in self._fallback_repositories:
1061
# Writes don't affect fallback repos
1063
elif self._lock_mode == 'r':
1064
raise errors.ReadOnlyError(self)
1066
self._lock_count += 1
1067
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1069
def leave_lock_in_place(self):
1070
if not self._lock_token:
1071
raise NotImplementedError(self.leave_lock_in_place)
1072
self._leave_lock = True
1074
def dont_leave_lock_in_place(self):
1075
if not self._lock_token:
1076
raise NotImplementedError(self.dont_leave_lock_in_place)
1077
self._leave_lock = False
1079
def _set_real_repository(self, repository):
1080
"""Set the _real_repository for this repository.
1082
:param repository: The repository to fallback to for non-hpss
1083
implemented operations.
1085
if self._real_repository is not None:
1086
# Replacing an already set real repository.
1087
# We cannot do this [currently] if the repository is locked -
1088
# synchronised state might be lost.
1089
if self.is_locked():
1090
raise AssertionError('_real_repository is already set')
1091
if isinstance(repository, RemoteRepository):
1092
raise AssertionError()
1093
self._real_repository = repository
1094
# three code paths happen here:
1095
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1096
# up stacking. In this case self._fallback_repositories is [], and the
1097
# real repo is already setup. Preserve the real repo and
1098
# RemoteRepository.add_fallback_repository will avoid adding
1100
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1101
# ensure_real is triggered from a branch, the real repository to
1102
# set already has a matching list with separate instances, but
1103
# as they are also RemoteRepositories we don't worry about making the
1104
# lists be identical.
1105
# 3) new servers, RemoteRepository.ensure_real is triggered before
1106
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1107
# and need to populate it.
1108
if (self._fallback_repositories and
1109
len(self._real_repository._fallback_repositories) !=
1110
len(self._fallback_repositories)):
1111
if len(self._real_repository._fallback_repositories):
1112
raise AssertionError(
1113
"cannot cleanly remove existing _fallback_repositories")
1114
for fb in self._fallback_repositories:
1115
self._real_repository.add_fallback_repository(fb)
1116
if self._lock_mode == 'w':
1117
# if we are already locked, the real repository must be able to
1118
# acquire the lock with our token.
1119
self._real_repository.lock_write(self._lock_token)
1120
elif self._lock_mode == 'r':
1121
self._real_repository.lock_read()
1123
def start_write_group(self):
1124
"""Start a write group on the decorated repository.
1126
Smart methods perform operations in a single step so this API
1127
is not really applicable except as a compatibility thunk
1128
for older plugins that don't use e.g. the CommitBuilder
1132
return self._real_repository.start_write_group()
1134
def _unlock(self, token):
1135
path = self.bzrdir._path_for_remote_call(self._client)
1137
# with no token the remote repository is not persistently locked.
1139
err_context = {'token': token}
1140
response = self._call('Repository.unlock', path, token,
1142
if response == ('ok',):
1145
raise errors.UnexpectedSmartServerResponse(response)
1147
@only_raises(errors.LockNotHeld, errors.LockBroken)
1149
if not self._lock_count:
1150
return lock.cant_unlock_not_held(self)
1151
self._lock_count -= 1
1152
if self._lock_count > 0:
1154
self._unstacked_provider.disable_cache()
1155
old_mode = self._lock_mode
1156
self._lock_mode = None
1158
# The real repository is responsible at present for raising an
1159
# exception if it's in an unfinished write group. However, it
1160
# normally will *not* actually remove the lock from disk - that's
1161
# done by the server on receiving the Repository.unlock call.
1162
# This is just to let the _real_repository stay up to date.
1163
if self._real_repository is not None:
1164
self._real_repository.unlock()
1166
# The rpc-level lock should be released even if there was a
1167
# problem releasing the vfs-based lock.
1169
# Only write-locked repositories need to make a remote method
1170
# call to perform the unlock.
1171
old_token = self._lock_token
1172
self._lock_token = None
1173
if not self._leave_lock:
1174
self._unlock(old_token)
1175
# Fallbacks are always 'lock_read()' so we don't pay attention to
1177
for repo in self._fallback_repositories:
1180
def break_lock(self):
1181
# should hand off to the network
1183
return self._real_repository.break_lock()
1185
def _get_tarball(self, compression):
1186
"""Return a TemporaryFile containing a repository tarball.
1188
Returns None if the server does not support sending tarballs.
1191
path = self.bzrdir._path_for_remote_call(self._client)
1193
response, protocol = self._call_expecting_body(
1194
'Repository.tarball', path, compression)
1195
except errors.UnknownSmartMethod:
1196
protocol.cancel_read_body()
1198
if response[0] == 'ok':
1199
# Extract the tarball and return it
1200
t = tempfile.NamedTemporaryFile()
1201
# TODO: rpc layer should read directly into it...
1202
t.write(protocol.read_body_bytes())
1205
raise errors.UnexpectedSmartServerResponse(response)
1207
def sprout(self, to_bzrdir, revision_id=None):
1208
# TODO: Option to control what format is created?
1210
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1212
dest_repo.fetch(self, revision_id=revision_id)
1215
### These methods are just thin shims to the VFS object for now.
1217
def revision_tree(self, revision_id):
1219
return self._real_repository.revision_tree(revision_id)
1221
def get_serializer_format(self):
1223
return self._real_repository.get_serializer_format()
1225
def get_commit_builder(self, branch, parents, config, timestamp=None,
1226
timezone=None, committer=None, revprops=None,
1228
# FIXME: It ought to be possible to call this without immediately
1229
# triggering _ensure_real. For now it's the easiest thing to do.
1231
real_repo = self._real_repository
1232
builder = real_repo.get_commit_builder(branch, parents,
1233
config, timestamp=timestamp, timezone=timezone,
1234
committer=committer, revprops=revprops, revision_id=revision_id)
1237
def add_fallback_repository(self, repository):
1238
"""Add a repository to use for looking up data not held locally.
1240
:param repository: A repository.
1242
if not self._format.supports_external_lookups:
1243
raise errors.UnstackableRepositoryFormat(
1244
self._format.network_name(), self.base)
1245
# We need to accumulate additional repositories here, to pass them in
1248
if self.is_locked():
1249
# We will call fallback.unlock() when we transition to the unlocked
1250
# state, so always add a lock here. If a caller passes us a locked
1251
# repository, they are responsible for unlocking it later.
1252
repository.lock_read()
1253
self._check_fallback_repository(repository)
1254
self._fallback_repositories.append(repository)
1255
# If self._real_repository was parameterised already (e.g. because a
1256
# _real_branch had its get_stacked_on_url method called), then the
1257
# repository to be added may already be in the _real_repositories list.
1258
if self._real_repository is not None:
1259
fallback_locations = [repo.user_url for repo in
1260
self._real_repository._fallback_repositories]
1261
if repository.user_url not in fallback_locations:
1262
self._real_repository.add_fallback_repository(repository)
1264
def _check_fallback_repository(self, repository):
1265
"""Check that this repository can fallback to repository safely.
1267
Raise an error if not.
1269
:param repository: A repository to fallback to.
1271
return _mod_repository.InterRepository._assert_same_model(
1274
def add_inventory(self, revid, inv, parents):
1276
return self._real_repository.add_inventory(revid, inv, parents)
1278
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1279
parents, basis_inv=None, propagate_caches=False):
1281
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1282
delta, new_revision_id, parents, basis_inv=basis_inv,
1283
propagate_caches=propagate_caches)
1285
def add_revision(self, rev_id, rev, inv=None, config=None):
1287
return self._real_repository.add_revision(
1288
rev_id, rev, inv=inv, config=config)
1291
def get_inventory(self, revision_id):
1293
return self._real_repository.get_inventory(revision_id)
1295
def iter_inventories(self, revision_ids, ordering=None):
1297
return self._real_repository.iter_inventories(revision_ids, ordering)
1300
def get_revision(self, revision_id):
1302
return self._real_repository.get_revision(revision_id)
1304
def get_transaction(self):
1306
return self._real_repository.get_transaction()
1309
def clone(self, a_bzrdir, revision_id=None):
1311
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1313
def make_working_trees(self):
1314
"""See Repository.make_working_trees"""
1316
return self._real_repository.make_working_trees()
1318
def refresh_data(self):
1319
"""Re-read any data needed to synchronise with disk.
1321
This method is intended to be called after another repository instance
1322
(such as one used by a smart server) has inserted data into the
1323
repository. On all repositories this will work outside of write groups.
1324
Some repository formats (pack and newer for bzrlib native formats)
1325
support refresh_data inside write groups. If called inside a write
1326
group on a repository that does not support refreshing in a write group
1327
IsInWriteGroupError will be raised.
1329
if self._real_repository is not None:
1330
self._real_repository.refresh_data()
1332
def revision_ids_to_search_result(self, result_set):
1333
"""Convert a set of revision ids to a graph SearchResult."""
1334
result_parents = set()
1335
for parents in self.get_graph().get_parent_map(
1336
result_set).itervalues():
1337
result_parents.update(parents)
1338
included_keys = result_set.intersection(result_parents)
1339
start_keys = result_set.difference(included_keys)
1340
exclude_keys = result_parents.difference(result_set)
1341
result = graph.SearchResult(start_keys, exclude_keys,
1342
len(result_set), result_set)
1346
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1347
"""Return the revision ids that other has that this does not.
1349
These are returned in topological order.
1351
revision_id: only return revision ids included by revision_id.
1353
return repository.InterRepository.get(
1354
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1356
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1358
# No base implementation to use as RemoteRepository is not a subclass
1359
# of Repository; so this is a copy of Repository.fetch().
1360
if fetch_spec is not None and revision_id is not None:
1361
raise AssertionError(
1362
"fetch_spec and revision_id are mutually exclusive.")
1363
if self.is_in_write_group():
1364
raise errors.InternalBzrError(
1365
"May not fetch while in a write group.")
1366
# fast path same-url fetch operations
1367
if (self.has_same_location(source)
1368
and fetch_spec is None
1369
and self._has_same_fallbacks(source)):
1370
# check that last_revision is in 'from' and then return a
1372
if (revision_id is not None and
1373
not revision.is_null(revision_id)):
1374
self.get_revision(revision_id)
1376
# if there is no specific appropriate InterRepository, this will get
1377
# the InterRepository base class, which raises an
1378
# IncompatibleRepositories when asked to fetch.
1379
inter = repository.InterRepository.get(source, self)
1380
return inter.fetch(revision_id=revision_id, pb=pb,
1381
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1383
def create_bundle(self, target, base, fileobj, format=None):
1385
self._real_repository.create_bundle(target, base, fileobj, format)
1388
def get_ancestry(self, revision_id, topo_sorted=True):
1390
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1392
def fileids_altered_by_revision_ids(self, revision_ids):
1394
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1396
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1398
return self._real_repository._get_versioned_file_checker(
1399
revisions, revision_versions_cache)
1401
def iter_files_bytes(self, desired_files):
1402
"""See Repository.iter_file_bytes.
1405
return self._real_repository.iter_files_bytes(desired_files)
1407
def get_parent_map(self, revision_ids):
1408
"""See bzrlib.Graph.get_parent_map()."""
1409
return self._make_parents_provider().get_parent_map(revision_ids)
1411
def _get_parent_map_rpc(self, keys):
1412
"""Helper for get_parent_map that performs the RPC."""
1413
medium = self._client._medium
1414
if medium._is_remote_before((1, 2)):
1415
# We already found out that the server can't understand
1416
# Repository.get_parent_map requests, so just fetch the whole
1419
# Note that this reads the whole graph, when only some keys are
1420
# wanted. On this old server there's no way (?) to get them all
1421
# in one go, and the user probably will have seen a warning about
1422
# the server being old anyhow.
1423
rg = self._get_revision_graph(None)
1424
# There is an API discrepancy between get_parent_map and
1425
# get_revision_graph. Specifically, a "key:()" pair in
1426
# get_revision_graph just means a node has no parents. For
1427
# "get_parent_map" it means the node is a ghost. So fix up the
1428
# graph to correct this.
1429
# https://bugs.launchpad.net/bzr/+bug/214894
1430
# There is one other "bug" which is that ghosts in
1431
# get_revision_graph() are not returned at all. But we won't worry
1432
# about that for now.
1433
for node_id, parent_ids in rg.iteritems():
1434
if parent_ids == ():
1435
rg[node_id] = (NULL_REVISION,)
1436
rg[NULL_REVISION] = ()
1441
raise ValueError('get_parent_map(None) is not valid')
1442
if NULL_REVISION in keys:
1443
keys.discard(NULL_REVISION)
1444
found_parents = {NULL_REVISION:()}
1446
return found_parents
1449
# TODO(Needs analysis): We could assume that the keys being requested
1450
# from get_parent_map are in a breadth first search, so typically they
1451
# will all be depth N from some common parent, and we don't have to
1452
# have the server iterate from the root parent, but rather from the
1453
# keys we're searching; and just tell the server the keyspace we
1454
# already have; but this may be more traffic again.
1456
# Transform self._parents_map into a search request recipe.
1457
# TODO: Manage this incrementally to avoid covering the same path
1458
# repeatedly. (The server will have to on each request, but the less
1459
# work done the better).
1461
# Negative caching notes:
1462
# new server sends missing when a request including the revid
1463
# 'include-missing:' is present in the request.
1464
# missing keys are serialised as missing:X, and we then call
1465
# provider.note_missing(X) for-all X
1466
parents_map = self._unstacked_provider.get_cached_map()
1467
if parents_map is None:
1468
# Repository is not locked, so there's no cache.
1470
# start_set is all the keys in the cache
1471
start_set = set(parents_map)
1472
# result set is all the references to keys in the cache
1473
result_parents = set()
1474
for parents in parents_map.itervalues():
1475
result_parents.update(parents)
1476
stop_keys = result_parents.difference(start_set)
1477
# We don't need to send ghosts back to the server as a position to
1479
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1480
key_count = len(parents_map)
1481
if (NULL_REVISION in result_parents
1482
and NULL_REVISION in self._unstacked_provider.missing_keys):
1483
# If we pruned NULL_REVISION from the stop_keys because it's also
1484
# in our cache of "missing" keys we need to increment our key count
1485
# by 1, because the reconsitituted SearchResult on the server will
1486
# still consider NULL_REVISION to be an included key.
1488
included_keys = start_set.intersection(result_parents)
1489
start_set.difference_update(included_keys)
1490
recipe = ('manual', start_set, stop_keys, key_count)
1491
body = self._serialise_search_recipe(recipe)
1492
path = self.bzrdir._path_for_remote_call(self._client)
1494
if type(key) is not str:
1496
"key %r not a plain string" % (key,))
1497
verb = 'Repository.get_parent_map'
1498
args = (path, 'include-missing:') + tuple(keys)
1500
response = self._call_with_body_bytes_expecting_body(
1502
except errors.UnknownSmartMethod:
1503
# Server does not support this method, so get the whole graph.
1504
# Worse, we have to force a disconnection, because the server now
1505
# doesn't realise it has a body on the wire to consume, so the
1506
# only way to recover is to abandon the connection.
1508
'Server is too old for fast get_parent_map, reconnecting. '
1509
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1511
# To avoid having to disconnect repeatedly, we keep track of the
1512
# fact the server doesn't understand remote methods added in 1.2.
1513
medium._remember_remote_is_before((1, 2))
1514
# Recurse just once and we should use the fallback code.
1515
return self._get_parent_map_rpc(keys)
1516
response_tuple, response_handler = response
1517
if response_tuple[0] not in ['ok']:
1518
response_handler.cancel_read_body()
1519
raise errors.UnexpectedSmartServerResponse(response_tuple)
1520
if response_tuple[0] == 'ok':
1521
coded = bz2.decompress(response_handler.read_body_bytes())
1523
# no revisions found
1525
lines = coded.split('\n')
1528
d = tuple(line.split())
1530
revision_graph[d[0]] = d[1:]
1533
if d[0].startswith('missing:'):
1535
self._unstacked_provider.note_missing_key(revid)
1537
# no parents - so give the Graph result
1539
revision_graph[d[0]] = (NULL_REVISION,)
1540
return revision_graph
1543
def get_signature_text(self, revision_id):
1545
return self._real_repository.get_signature_text(revision_id)
1548
def _get_inventory_xml(self, revision_id):
1550
return self._real_repository._get_inventory_xml(revision_id)
1552
def reconcile(self, other=None, thorough=False):
1554
return self._real_repository.reconcile(other=other, thorough=thorough)
1556
def all_revision_ids(self):
1558
return self._real_repository.all_revision_ids()
1561
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1563
return self._real_repository.get_deltas_for_revisions(revisions,
1564
specific_fileids=specific_fileids)
1567
def get_revision_delta(self, revision_id, specific_fileids=None):
1569
return self._real_repository.get_revision_delta(revision_id,
1570
specific_fileids=specific_fileids)
1573
def revision_trees(self, revision_ids):
1575
return self._real_repository.revision_trees(revision_ids)
1578
def get_revision_reconcile(self, revision_id):
1580
return self._real_repository.get_revision_reconcile(revision_id)
1583
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1585
return self._real_repository.check(revision_ids=revision_ids,
1586
callback_refs=callback_refs, check_repo=check_repo)
1588
def copy_content_into(self, destination, revision_id=None):
1590
return self._real_repository.copy_content_into(
1591
destination, revision_id=revision_id)
1593
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1594
# get a tarball of the remote repository, and copy from that into the
1596
from bzrlib import osutils
1598
# TODO: Maybe a progress bar while streaming the tarball?
1599
note("Copying repository content as tarball...")
1600
tar_file = self._get_tarball('bz2')
1601
if tar_file is None:
1603
destination = to_bzrdir.create_repository()
1605
tar = tarfile.open('repository', fileobj=tar_file,
1607
tmpdir = osutils.mkdtemp()
1609
_extract_tar(tar, tmpdir)
1610
tmp_bzrdir = BzrDir.open(tmpdir)
1611
tmp_repo = tmp_bzrdir.open_repository()
1612
tmp_repo.copy_content_into(destination, revision_id)
1614
osutils.rmtree(tmpdir)
1618
# TODO: Suggestion from john: using external tar is much faster than
1619
# python's tarfile library, but it may not work on windows.
1622
def inventories(self):
1623
"""Decorate the real repository for now.
1625
In the long term a full blown network facility is needed to
1626
avoid creating a real repository object locally.
1629
return self._real_repository.inventories
1632
def pack(self, hint=None, clean_obsolete_packs=False):
1633
"""Compress the data within the repository.
1635
This is not currently implemented within the smart server.
1638
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1641
def revisions(self):
1642
"""Decorate the real repository for now.
1644
In the short term this should become a real object to intercept graph
1647
In the long term a full blown network facility is needed.
1650
return self._real_repository.revisions
1652
def set_make_working_trees(self, new_value):
1654
new_value_str = "True"
1656
new_value_str = "False"
1657
path = self.bzrdir._path_for_remote_call(self._client)
1659
response = self._call(
1660
'Repository.set_make_working_trees', path, new_value_str)
1661
except errors.UnknownSmartMethod:
1663
self._real_repository.set_make_working_trees(new_value)
1665
if response[0] != 'ok':
1666
raise errors.UnexpectedSmartServerResponse(response)
1669
def signatures(self):
1670
"""Decorate the real repository for now.
1672
In the long term a full blown network facility is needed to avoid
1673
creating a real repository object locally.
1676
return self._real_repository.signatures
1679
def sign_revision(self, revision_id, gpg_strategy):
1681
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1685
"""Decorate the real repository for now.
1687
In the long term a full blown network facility is needed to avoid
1688
creating a real repository object locally.
1691
return self._real_repository.texts
1694
def get_revisions(self, revision_ids):
1696
return self._real_repository.get_revisions(revision_ids)
1698
def supports_rich_root(self):
1699
return self._format.rich_root_data
1701
def iter_reverse_revision_history(self, revision_id):
1703
return self._real_repository.iter_reverse_revision_history(revision_id)
1706
def _serializer(self):
1707
return self._format._serializer
1709
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1711
return self._real_repository.store_revision_signature(
1712
gpg_strategy, plaintext, revision_id)
1714
def add_signature_text(self, revision_id, signature):
1716
return self._real_repository.add_signature_text(revision_id, signature)
1718
def has_signature_for_revision_id(self, revision_id):
1720
return self._real_repository.has_signature_for_revision_id(revision_id)
1722
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1724
return self._real_repository.item_keys_introduced_by(revision_ids,
1725
_files_pb=_files_pb)
1727
def revision_graph_can_have_wrong_parents(self):
1728
# The answer depends on the remote repo format.
1730
return self._real_repository.revision_graph_can_have_wrong_parents()
1732
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1734
return self._real_repository._find_inconsistent_revision_parents(
1737
def _check_for_inconsistent_revision_parents(self):
1739
return self._real_repository._check_for_inconsistent_revision_parents()
1741
def _make_parents_provider(self, other=None):
1742
providers = [self._unstacked_provider]
1743
if other is not None:
1744
providers.insert(0, other)
1745
providers.extend(r._make_parents_provider() for r in
1746
self._fallback_repositories)
1747
return graph.StackedParentsProvider(providers)
1749
def _serialise_search_recipe(self, recipe):
1750
"""Serialise a graph search recipe.
1752
:param recipe: A search recipe (start, stop, count).
1753
:return: Serialised bytes.
1755
start_keys = ' '.join(recipe[1])
1756
stop_keys = ' '.join(recipe[2])
1757
count = str(recipe[3])
1758
return '\n'.join((start_keys, stop_keys, count))
1760
def _serialise_search_result(self, search_result):
1761
if isinstance(search_result, graph.PendingAncestryResult):
1762
parts = ['ancestry-of']
1763
parts.extend(search_result.heads)
1765
recipe = search_result.get_recipe()
1766
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1767
return '\n'.join(parts)
1770
path = self.bzrdir._path_for_remote_call(self._client)
1772
response = self._call('PackRepository.autopack', path)
1773
except errors.UnknownSmartMethod:
1775
self._real_repository._pack_collection.autopack()
1778
if response[0] != 'ok':
1779
raise errors.UnexpectedSmartServerResponse(response)
1782
class RemoteStreamSink(repository.StreamSink):
1784
def _insert_real(self, stream, src_format, resume_tokens):
1785
self.target_repo._ensure_real()
1786
sink = self.target_repo._real_repository._get_sink()
1787
result = sink.insert_stream(stream, src_format, resume_tokens)
1789
self.target_repo.autopack()
1792
def insert_stream(self, stream, src_format, resume_tokens):
1793
target = self.target_repo
1794
target._unstacked_provider.missing_keys.clear()
1795
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1796
if target._lock_token:
1797
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1798
lock_args = (target._lock_token or '',)
1800
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1802
client = target._client
1803
medium = client._medium
1804
path = target.bzrdir._path_for_remote_call(client)
1805
# Probe for the verb to use with an empty stream before sending the
1806
# real stream to it. We do this both to avoid the risk of sending a
1807
# large request that is then rejected, and because we don't want to
1808
# implement a way to buffer, rewind, or restart the stream.
1810
for verb, required_version in candidate_calls:
1811
if medium._is_remote_before(required_version):
1814
# We've already done the probing (and set _is_remote_before) on
1815
# a previous insert.
1818
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1820
response = client.call_with_body_stream(
1821
(verb, path, '') + lock_args, byte_stream)
1822
except errors.UnknownSmartMethod:
1823
medium._remember_remote_is_before(required_version)
1829
return self._insert_real(stream, src_format, resume_tokens)
1830
self._last_inv_record = None
1831
self._last_substream = None
1832
if required_version < (1, 19):
1833
# Remote side doesn't support inventory deltas. Wrap the stream to
1834
# make sure we don't send any. If the stream contains inventory
1835
# deltas we'll interrupt the smart insert_stream request and
1837
stream = self._stop_stream_if_inventory_delta(stream)
1838
byte_stream = smart_repo._stream_to_byte_stream(
1840
resume_tokens = ' '.join(resume_tokens)
1841
response = client.call_with_body_stream(
1842
(verb, path, resume_tokens) + lock_args, byte_stream)
1843
if response[0][0] not in ('ok', 'missing-basis'):
1844
raise errors.UnexpectedSmartServerResponse(response)
1845
if self._last_substream is not None:
1846
# The stream included an inventory-delta record, but the remote
1847
# side isn't new enough to support them. So we need to send the
1848
# rest of the stream via VFS.
1849
self.target_repo.refresh_data()
1850
return self._resume_stream_with_vfs(response, src_format)
1851
if response[0][0] == 'missing-basis':
1852
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1853
resume_tokens = tokens
1854
return resume_tokens, set(missing_keys)
1856
self.target_repo.refresh_data()
1859
def _resume_stream_with_vfs(self, response, src_format):
1860
"""Resume sending a stream via VFS, first resending the record and
1861
substream that couldn't be sent via an insert_stream verb.
1863
if response[0][0] == 'missing-basis':
1864
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1865
# Ignore missing_keys, we haven't finished inserting yet
1868
def resume_substream():
1869
# Yield the substream that was interrupted.
1870
for record in self._last_substream:
1872
self._last_substream = None
1873
def resume_stream():
1874
# Finish sending the interrupted substream
1875
yield ('inventory-deltas', resume_substream())
1876
# Then simply continue sending the rest of the stream.
1877
for substream_kind, substream in self._last_stream:
1878
yield substream_kind, substream
1879
return self._insert_real(resume_stream(), src_format, tokens)
1881
def _stop_stream_if_inventory_delta(self, stream):
1882
"""Normally this just lets the original stream pass-through unchanged.
1884
However if any 'inventory-deltas' substream occurs it will stop
1885
streaming, and store the interrupted substream and stream in
1886
self._last_substream and self._last_stream so that the stream can be
1887
resumed by _resume_stream_with_vfs.
1890
stream_iter = iter(stream)
1891
for substream_kind, substream in stream_iter:
1892
if substream_kind == 'inventory-deltas':
1893
self._last_substream = substream
1894
self._last_stream = stream_iter
1897
yield substream_kind, substream
1900
class RemoteStreamSource(repository.StreamSource):
1901
"""Stream data from a remote server."""
1903
def get_stream(self, search):
1904
if (self.from_repository._fallback_repositories and
1905
self.to_format._fetch_order == 'topological'):
1906
return self._real_stream(self.from_repository, search)
1909
repos = [self.from_repository]
1915
repos.extend(repo._fallback_repositories)
1916
sources.append(repo)
1917
return self.missing_parents_chain(search, sources)
1919
def get_stream_for_missing_keys(self, missing_keys):
1920
self.from_repository._ensure_real()
1921
real_repo = self.from_repository._real_repository
1922
real_source = real_repo._get_source(self.to_format)
1923
return real_source.get_stream_for_missing_keys(missing_keys)
1925
def _real_stream(self, repo, search):
1926
"""Get a stream for search from repo.
1928
This never called RemoteStreamSource.get_stream, and is a heler
1929
for RemoteStreamSource._get_stream to allow getting a stream
1930
reliably whether fallback back because of old servers or trying
1931
to stream from a non-RemoteRepository (which the stacked support
1934
source = repo._get_source(self.to_format)
1935
if isinstance(source, RemoteStreamSource):
1937
source = repo._real_repository._get_source(self.to_format)
1938
return source.get_stream(search)
1940
def _get_stream(self, repo, search):
1941
"""Core worker to get a stream from repo for search.
1943
This is used by both get_stream and the stacking support logic. It
1944
deliberately gets a stream for repo which does not need to be
1945
self.from_repository. In the event that repo is not Remote, or
1946
cannot do a smart stream, a fallback is made to the generic
1947
repository._get_stream() interface, via self._real_stream.
1949
In the event of stacking, streams from _get_stream will not
1950
contain all the data for search - this is normal (see get_stream).
1952
:param repo: A repository.
1953
:param search: A search.
1955
# Fallbacks may be non-smart
1956
if not isinstance(repo, RemoteRepository):
1957
return self._real_stream(repo, search)
1958
client = repo._client
1959
medium = client._medium
1960
path = repo.bzrdir._path_for_remote_call(client)
1961
search_bytes = repo._serialise_search_result(search)
1962
args = (path, self.to_format.network_name())
1964
('Repository.get_stream_1.19', (1, 19)),
1965
('Repository.get_stream', (1, 13))]
1967
for verb, version in candidate_verbs:
1968
if medium._is_remote_before(version):
1971
response = repo._call_with_body_bytes_expecting_body(
1972
verb, args, search_bytes)
1973
except errors.UnknownSmartMethod:
1974
medium._remember_remote_is_before(version)
1976
response_tuple, response_handler = response
1980
return self._real_stream(repo, search)
1981
if response_tuple[0] != 'ok':
1982
raise errors.UnexpectedSmartServerResponse(response_tuple)
1983
byte_stream = response_handler.read_streamed_body()
1984
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
1985
self._record_counter)
1986
if src_format.network_name() != repo._format.network_name():
1987
raise AssertionError(
1988
"Mismatched RemoteRepository and stream src %r, %r" % (
1989
src_format.network_name(), repo._format.network_name()))
1992
def missing_parents_chain(self, search, sources):
1993
"""Chain multiple streams together to handle stacking.
1995
:param search: The overall search to satisfy with streams.
1996
:param sources: A list of Repository objects to query.
1998
self.from_serialiser = self.from_repository._format._serializer
1999
self.seen_revs = set()
2000
self.referenced_revs = set()
2001
# If there are heads in the search, or the key count is > 0, we are not
2003
while not search.is_empty() and len(sources) > 1:
2004
source = sources.pop(0)
2005
stream = self._get_stream(source, search)
2006
for kind, substream in stream:
2007
if kind != 'revisions':
2008
yield kind, substream
2010
yield kind, self.missing_parents_rev_handler(substream)
2011
search = search.refine(self.seen_revs, self.referenced_revs)
2012
self.seen_revs = set()
2013
self.referenced_revs = set()
2014
if not search.is_empty():
2015
for kind, stream in self._get_stream(sources[0], search):
2018
def missing_parents_rev_handler(self, substream):
2019
for content in substream:
2020
revision_bytes = content.get_bytes_as('fulltext')
2021
revision = self.from_serialiser.read_revision_from_string(
2023
self.seen_revs.add(content.key[-1])
2024
self.referenced_revs.update(revision.parent_ids)
2028
class RemoteBranchLockableFiles(LockableFiles):
2029
"""A 'LockableFiles' implementation that talks to a smart server.
2031
This is not a public interface class.
2034
def __init__(self, bzrdir, _client):
2035
self.bzrdir = bzrdir
2036
self._client = _client
2037
self._need_find_modes = True
2038
LockableFiles.__init__(
2039
self, bzrdir.get_branch_transport(None),
2040
'lock', lockdir.LockDir)
2042
def _find_modes(self):
2043
# RemoteBranches don't let the client set the mode of control files.
2044
self._dir_mode = None
2045
self._file_mode = None
2048
class RemoteBranchFormat(branch.BranchFormat):
2050
def __init__(self, network_name=None):
2051
super(RemoteBranchFormat, self).__init__()
2052
self._matchingbzrdir = RemoteBzrDirFormat()
2053
self._matchingbzrdir.set_branch_format(self)
2054
self._custom_format = None
2055
self._network_name = network_name
2057
def __eq__(self, other):
2058
return (isinstance(other, RemoteBranchFormat) and
2059
self.__dict__ == other.__dict__)
2061
def _ensure_real(self):
2062
if self._custom_format is None:
2063
self._custom_format = branch.network_format_registry.get(
2066
def get_format_description(self):
2068
return 'Remote: ' + self._custom_format.get_format_description()
2070
def network_name(self):
2071
return self._network_name
2073
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2074
return a_bzrdir.open_branch(name=name,
2075
ignore_fallbacks=ignore_fallbacks)
2077
def _vfs_initialize(self, a_bzrdir, name):
2078
# Initialisation when using a local bzrdir object, or a non-vfs init
2079
# method is not available on the server.
2080
# self._custom_format is always set - the start of initialize ensures
2082
if isinstance(a_bzrdir, RemoteBzrDir):
2083
a_bzrdir._ensure_real()
2084
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2087
# We assume the bzrdir is parameterised; it may not be.
2088
result = self._custom_format.initialize(a_bzrdir, name)
2089
if (isinstance(a_bzrdir, RemoteBzrDir) and
2090
not isinstance(result, RemoteBranch)):
2091
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2095
def initialize(self, a_bzrdir, name=None):
2096
# 1) get the network name to use.
2097
if self._custom_format:
2098
network_name = self._custom_format.network_name()
2100
# Select the current bzrlib default and ask for that.
2101
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2102
reference_format = reference_bzrdir_format.get_branch_format()
2103
self._custom_format = reference_format
2104
network_name = reference_format.network_name()
2105
# Being asked to create on a non RemoteBzrDir:
2106
if not isinstance(a_bzrdir, RemoteBzrDir):
2107
return self._vfs_initialize(a_bzrdir, name=name)
2108
medium = a_bzrdir._client._medium
2109
if medium._is_remote_before((1, 13)):
2110
return self._vfs_initialize(a_bzrdir, name=name)
2111
# Creating on a remote bzr dir.
2112
# 2) try direct creation via RPC
2113
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2114
if name is not None:
2115
# XXX JRV20100304: Support creating colocated branches
2116
raise errors.NoColocatedBranchSupport(self)
2117
verb = 'BzrDir.create_branch'
2119
response = a_bzrdir._call(verb, path, network_name)
2120
except errors.UnknownSmartMethod:
2121
# Fallback - use vfs methods
2122
medium._remember_remote_is_before((1, 13))
2123
return self._vfs_initialize(a_bzrdir, name=name)
2124
if response[0] != 'ok':
2125
raise errors.UnexpectedSmartServerResponse(response)
2126
# Turn the response into a RemoteRepository object.
2127
format = RemoteBranchFormat(network_name=response[1])
2128
repo_format = response_tuple_to_repo_format(response[3:])
2129
if response[2] == '':
2130
repo_bzrdir = a_bzrdir
2132
repo_bzrdir = RemoteBzrDir(
2133
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2135
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2136
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2137
format=format, setup_stacking=False, name=name)
2138
# XXX: We know this is a new branch, so it must have revno 0, revid
2139
# NULL_REVISION. Creating the branch locked would make this be unable
2140
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2141
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2142
return remote_branch
2144
def make_tags(self, branch):
2146
return self._custom_format.make_tags(branch)
2148
def supports_tags(self):
2149
# Remote branches might support tags, but we won't know until we
2150
# access the real remote branch.
2152
return self._custom_format.supports_tags()
2154
def supports_stacking(self):
2156
return self._custom_format.supports_stacking()
2158
def supports_set_append_revisions_only(self):
2160
return self._custom_format.supports_set_append_revisions_only()
2163
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2164
"""Branch stored on a server accessed by HPSS RPC.
2166
At the moment most operations are mapped down to simple file operations.
2169
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2170
_client=None, format=None, setup_stacking=True, name=None):
2171
"""Create a RemoteBranch instance.
2173
:param real_branch: An optional local implementation of the branch
2174
format, usually accessing the data via the VFS.
2175
:param _client: Private parameter for testing.
2176
:param format: A RemoteBranchFormat object, None to create one
2177
automatically. If supplied it should have a network_name already
2179
:param setup_stacking: If True make an RPC call to determine the
2180
stacked (or not) status of the branch. If False assume the branch
2182
:param name: Colocated branch name
2184
# We intentionally don't call the parent class's __init__, because it
2185
# will try to assign to self.tags, which is a property in this subclass.
2186
# And the parent's __init__ doesn't do much anyway.
2187
self.bzrdir = remote_bzrdir
2188
if _client is not None:
2189
self._client = _client
2191
self._client = remote_bzrdir._client
2192
self.repository = remote_repository
2193
if real_branch is not None:
2194
self._real_branch = real_branch
2195
# Give the remote repository the matching real repo.
2196
real_repo = self._real_branch.repository
2197
if isinstance(real_repo, RemoteRepository):
2198
real_repo._ensure_real()
2199
real_repo = real_repo._real_repository
2200
self.repository._set_real_repository(real_repo)
2201
# Give the branch the remote repository to let fast-pathing happen.
2202
self._real_branch.repository = self.repository
2204
self._real_branch = None
2205
# Fill out expected attributes of branch for bzrlib API users.
2206
self._clear_cached_state()
2207
# TODO: deprecate self.base in favor of user_url
2208
self.base = self.bzrdir.user_url
2210
self._control_files = None
2211
self._lock_mode = None
2212
self._lock_token = None
2213
self._repo_lock_token = None
2214
self._lock_count = 0
2215
self._leave_lock = False
2216
# Setup a format: note that we cannot call _ensure_real until all the
2217
# attributes above are set: This code cannot be moved higher up in this
2220
self._format = RemoteBranchFormat()
2221
if real_branch is not None:
2222
self._format._network_name = \
2223
self._real_branch._format.network_name()
2225
self._format = format
2226
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2227
# branch.open_branch method.
2228
self._real_ignore_fallbacks = not setup_stacking
2229
if not self._format._network_name:
2230
# Did not get from open_branchV2 - old server.
2232
self._format._network_name = \
2233
self._real_branch._format.network_name()
2234
self.tags = self._format.make_tags(self)
2235
# The base class init is not called, so we duplicate this:
2236
hooks = branch.Branch.hooks['open']
2239
self._is_stacked = False
2241
self._setup_stacking()
2243
def _setup_stacking(self):
2244
# configure stacking into the remote repository, by reading it from
2247
fallback_url = self.get_stacked_on_url()
2248
except (errors.NotStacked, errors.UnstackableBranchFormat,
2249
errors.UnstackableRepositoryFormat), e:
2251
self._is_stacked = True
2252
self._activate_fallback_location(fallback_url)
2254
def _get_config(self):
2255
return RemoteBranchConfig(self)
2257
def _get_real_transport(self):
2258
# if we try vfs access, return the real branch's vfs transport
2260
return self._real_branch._transport
2262
_transport = property(_get_real_transport)
2265
return "%s(%s)" % (self.__class__.__name__, self.base)
2269
def _ensure_real(self):
2270
"""Ensure that there is a _real_branch set.
2272
Used before calls to self._real_branch.
2274
if self._real_branch is None:
2275
if not vfs.vfs_enabled():
2276
raise AssertionError('smart server vfs must be enabled '
2277
'to use vfs implementation')
2278
self.bzrdir._ensure_real()
2279
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2280
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2281
if self.repository._real_repository is None:
2282
# Give the remote repository the matching real repo.
2283
real_repo = self._real_branch.repository
2284
if isinstance(real_repo, RemoteRepository):
2285
real_repo._ensure_real()
2286
real_repo = real_repo._real_repository
2287
self.repository._set_real_repository(real_repo)
2288
# Give the real branch the remote repository to let fast-pathing
2290
self._real_branch.repository = self.repository
2291
if self._lock_mode == 'r':
2292
self._real_branch.lock_read()
2293
elif self._lock_mode == 'w':
2294
self._real_branch.lock_write(token=self._lock_token)
2296
def _translate_error(self, err, **context):
2297
self.repository._translate_error(err, branch=self, **context)
2299
def _clear_cached_state(self):
2300
super(RemoteBranch, self)._clear_cached_state()
2301
if self._real_branch is not None:
2302
self._real_branch._clear_cached_state()
2304
def _clear_cached_state_of_remote_branch_only(self):
2305
"""Like _clear_cached_state, but doesn't clear the cache of
2308
This is useful when falling back to calling a method of
2309
self._real_branch that changes state. In that case the underlying
2310
branch changes, so we need to invalidate this RemoteBranch's cache of
2311
it. However, there's no need to invalidate the _real_branch's cache
2312
too, in fact doing so might harm performance.
2314
super(RemoteBranch, self)._clear_cached_state()
2317
def control_files(self):
2318
# Defer actually creating RemoteBranchLockableFiles until its needed,
2319
# because it triggers an _ensure_real that we otherwise might not need.
2320
if self._control_files is None:
2321
self._control_files = RemoteBranchLockableFiles(
2322
self.bzrdir, self._client)
2323
return self._control_files
2325
def _get_checkout_format(self):
2327
return self._real_branch._get_checkout_format()
2329
def get_physical_lock_status(self):
2330
"""See Branch.get_physical_lock_status()."""
2331
# should be an API call to the server, as branches must be lockable.
2333
return self._real_branch.get_physical_lock_status()
2335
def get_stacked_on_url(self):
2336
"""Get the URL this branch is stacked against.
2338
:raises NotStacked: If the branch is not stacked.
2339
:raises UnstackableBranchFormat: If the branch does not support
2341
:raises UnstackableRepositoryFormat: If the repository does not support
2345
# there may not be a repository yet, so we can't use
2346
# self._translate_error, so we can't use self._call either.
2347
response = self._client.call('Branch.get_stacked_on_url',
2348
self._remote_path())
2349
except errors.ErrorFromSmartServer, err:
2350
# there may not be a repository yet, so we can't call through
2351
# its _translate_error
2352
_translate_error(err, branch=self)
2353
except errors.UnknownSmartMethod, err:
2355
return self._real_branch.get_stacked_on_url()
2356
if response[0] != 'ok':
2357
raise errors.UnexpectedSmartServerResponse(response)
2360
def set_stacked_on_url(self, url):
2361
branch.Branch.set_stacked_on_url(self, url)
2363
self._is_stacked = False
2365
self._is_stacked = True
2367
def _vfs_get_tags_bytes(self):
2369
return self._real_branch._get_tags_bytes()
2371
def _get_tags_bytes(self):
2372
medium = self._client._medium
2373
if medium._is_remote_before((1, 13)):
2374
return self._vfs_get_tags_bytes()
2376
response = self._call('Branch.get_tags_bytes', self._remote_path())
2377
except errors.UnknownSmartMethod:
2378
medium._remember_remote_is_before((1, 13))
2379
return self._vfs_get_tags_bytes()
2382
def _vfs_set_tags_bytes(self, bytes):
2384
return self._real_branch._set_tags_bytes(bytes)
2386
def _set_tags_bytes(self, bytes):
2387
medium = self._client._medium
2388
if medium._is_remote_before((1, 18)):
2389
self._vfs_set_tags_bytes(bytes)
2393
self._remote_path(), self._lock_token, self._repo_lock_token)
2394
response = self._call_with_body_bytes(
2395
'Branch.set_tags_bytes', args, bytes)
2396
except errors.UnknownSmartMethod:
2397
medium._remember_remote_is_before((1, 18))
2398
self._vfs_set_tags_bytes(bytes)
2400
def lock_read(self):
2401
"""Lock the branch for read operations.
2403
:return: A bzrlib.lock.LogicalLockResult.
2405
self.repository.lock_read()
2406
if not self._lock_mode:
2407
self._note_lock('r')
2408
self._lock_mode = 'r'
2409
self._lock_count = 1
2410
if self._real_branch is not None:
2411
self._real_branch.lock_read()
2413
self._lock_count += 1
2414
return lock.LogicalLockResult(self.unlock)
2416
def _remote_lock_write(self, token):
2418
branch_token = repo_token = ''
2420
branch_token = token
2421
repo_token = self.repository.lock_write().repository_token
2422
self.repository.unlock()
2423
err_context = {'token': token}
2425
response = self._call(
2426
'Branch.lock_write', self._remote_path(), branch_token,
2427
repo_token or '', **err_context)
2428
except errors.LockContention, e:
2429
# The LockContention from the server doesn't have any
2430
# information about the lock_url. We re-raise LockContention
2431
# with valid lock_url.
2432
raise errors.LockContention('(remote lock)',
2433
self.repository.base.split('.bzr/')[0])
2434
if response[0] != 'ok':
2435
raise errors.UnexpectedSmartServerResponse(response)
2436
ok, branch_token, repo_token = response
2437
return branch_token, repo_token
2439
def lock_write(self, token=None):
2440
if not self._lock_mode:
2441
self._note_lock('w')
2442
# Lock the branch and repo in one remote call.
2443
remote_tokens = self._remote_lock_write(token)
2444
self._lock_token, self._repo_lock_token = remote_tokens
2445
if not self._lock_token:
2446
raise SmartProtocolError('Remote server did not return a token!')
2447
# Tell the self.repository object that it is locked.
2448
self.repository.lock_write(
2449
self._repo_lock_token, _skip_rpc=True)
2451
if self._real_branch is not None:
2452
self._real_branch.lock_write(token=self._lock_token)
2453
if token is not None:
2454
self._leave_lock = True
2456
self._leave_lock = False
2457
self._lock_mode = 'w'
2458
self._lock_count = 1
2459
elif self._lock_mode == 'r':
2460
raise errors.ReadOnlyError(self)
2462
if token is not None:
2463
# A token was given to lock_write, and we're relocking, so
2464
# check that the given token actually matches the one we
2466
if token != self._lock_token:
2467
raise errors.TokenMismatch(token, self._lock_token)
2468
self._lock_count += 1
2469
# Re-lock the repository too.
2470
self.repository.lock_write(self._repo_lock_token)
2471
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2473
def _unlock(self, branch_token, repo_token):
2474
err_context = {'token': str((branch_token, repo_token))}
2475
response = self._call(
2476
'Branch.unlock', self._remote_path(), branch_token,
2477
repo_token or '', **err_context)
2478
if response == ('ok',):
2480
raise errors.UnexpectedSmartServerResponse(response)
2482
@only_raises(errors.LockNotHeld, errors.LockBroken)
2485
self._lock_count -= 1
2486
if not self._lock_count:
2487
self._clear_cached_state()
2488
mode = self._lock_mode
2489
self._lock_mode = None
2490
if self._real_branch is not None:
2491
if (not self._leave_lock and mode == 'w' and
2492
self._repo_lock_token):
2493
# If this RemoteBranch will remove the physical lock
2494
# for the repository, make sure the _real_branch
2495
# doesn't do it first. (Because the _real_branch's
2496
# repository is set to be the RemoteRepository.)
2497
self._real_branch.repository.leave_lock_in_place()
2498
self._real_branch.unlock()
2500
# Only write-locked branched need to make a remote method
2501
# call to perform the unlock.
2503
if not self._lock_token:
2504
raise AssertionError('Locked, but no token!')
2505
branch_token = self._lock_token
2506
repo_token = self._repo_lock_token
2507
self._lock_token = None
2508
self._repo_lock_token = None
2509
if not self._leave_lock:
2510
self._unlock(branch_token, repo_token)
2512
self.repository.unlock()
2514
def break_lock(self):
2516
return self._real_branch.break_lock()
2518
def leave_lock_in_place(self):
2519
if not self._lock_token:
2520
raise NotImplementedError(self.leave_lock_in_place)
2521
self._leave_lock = True
2523
def dont_leave_lock_in_place(self):
2524
if not self._lock_token:
2525
raise NotImplementedError(self.dont_leave_lock_in_place)
2526
self._leave_lock = False
2529
def get_rev_id(self, revno, history=None):
2531
return _mod_revision.NULL_REVISION
2532
last_revision_info = self.last_revision_info()
2533
ok, result = self.repository.get_rev_id_for_revno(
2534
revno, last_revision_info)
2537
missing_parent = result[1]
2538
# Either the revision named by the server is missing, or its parent
2539
# is. Call get_parent_map to determine which, so that we report a
2541
parent_map = self.repository.get_parent_map([missing_parent])
2542
if missing_parent in parent_map:
2543
missing_parent = parent_map[missing_parent]
2544
raise errors.RevisionNotPresent(missing_parent, self.repository)
2546
def _last_revision_info(self):
2547
response = self._call('Branch.last_revision_info', self._remote_path())
2548
if response[0] != 'ok':
2549
raise SmartProtocolError('unexpected response code %s' % (response,))
2550
revno = int(response[1])
2551
last_revision = response[2]
2552
return (revno, last_revision)
2554
def _gen_revision_history(self):
2555
"""See Branch._gen_revision_history()."""
2556
if self._is_stacked:
2558
return self._real_branch._gen_revision_history()
2559
response_tuple, response_handler = self._call_expecting_body(
2560
'Branch.revision_history', self._remote_path())
2561
if response_tuple[0] != 'ok':
2562
raise errors.UnexpectedSmartServerResponse(response_tuple)
2563
result = response_handler.read_body_bytes().split('\x00')
2568
def _remote_path(self):
2569
return self.bzrdir._path_for_remote_call(self._client)
2571
def _set_last_revision_descendant(self, revision_id, other_branch,
2572
allow_diverged=False, allow_overwrite_descendant=False):
2573
# This performs additional work to meet the hook contract; while its
2574
# undesirable, we have to synthesise the revno to call the hook, and
2575
# not calling the hook is worse as it means changes can't be prevented.
2576
# Having calculated this though, we can't just call into
2577
# set_last_revision_info as a simple call, because there is a set_rh
2578
# hook that some folk may still be using.
2579
old_revno, old_revid = self.last_revision_info()
2580
history = self._lefthand_history(revision_id)
2581
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2582
err_context = {'other_branch': other_branch}
2583
response = self._call('Branch.set_last_revision_ex',
2584
self._remote_path(), self._lock_token, self._repo_lock_token,
2585
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2587
self._clear_cached_state()
2588
if len(response) != 3 and response[0] != 'ok':
2589
raise errors.UnexpectedSmartServerResponse(response)
2590
new_revno, new_revision_id = response[1:]
2591
self._last_revision_info_cache = new_revno, new_revision_id
2592
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2593
if self._real_branch is not None:
2594
cache = new_revno, new_revision_id
2595
self._real_branch._last_revision_info_cache = cache
2597
def _set_last_revision(self, revision_id):
2598
old_revno, old_revid = self.last_revision_info()
2599
# This performs additional work to meet the hook contract; while its
2600
# undesirable, we have to synthesise the revno to call the hook, and
2601
# not calling the hook is worse as it means changes can't be prevented.
2602
# Having calculated this though, we can't just call into
2603
# set_last_revision_info as a simple call, because there is a set_rh
2604
# hook that some folk may still be using.
2605
history = self._lefthand_history(revision_id)
2606
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2607
self._clear_cached_state()
2608
response = self._call('Branch.set_last_revision',
2609
self._remote_path(), self._lock_token, self._repo_lock_token,
2611
if response != ('ok',):
2612
raise errors.UnexpectedSmartServerResponse(response)
2613
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2616
def set_revision_history(self, rev_history):
2617
# Send just the tip revision of the history; the server will generate
2618
# the full history from that. If the revision doesn't exist in this
2619
# branch, NoSuchRevision will be raised.
2620
if rev_history == []:
2623
rev_id = rev_history[-1]
2624
self._set_last_revision(rev_id)
2625
for hook in branch.Branch.hooks['set_rh']:
2626
hook(self, rev_history)
2627
self._cache_revision_history(rev_history)
2629
def _get_parent_location(self):
2630
medium = self._client._medium
2631
if medium._is_remote_before((1, 13)):
2632
return self._vfs_get_parent_location()
2634
response = self._call('Branch.get_parent', self._remote_path())
2635
except errors.UnknownSmartMethod:
2636
medium._remember_remote_is_before((1, 13))
2637
return self._vfs_get_parent_location()
2638
if len(response) != 1:
2639
raise errors.UnexpectedSmartServerResponse(response)
2640
parent_location = response[0]
2641
if parent_location == '':
2643
return parent_location
2645
def _vfs_get_parent_location(self):
2647
return self._real_branch._get_parent_location()
2649
def _set_parent_location(self, url):
2650
medium = self._client._medium
2651
if medium._is_remote_before((1, 15)):
2652
return self._vfs_set_parent_location(url)
2654
call_url = url or ''
2655
if type(call_url) is not str:
2656
raise AssertionError('url must be a str or None (%s)' % url)
2657
response = self._call('Branch.set_parent_location',
2658
self._remote_path(), self._lock_token, self._repo_lock_token,
2660
except errors.UnknownSmartMethod:
2661
medium._remember_remote_is_before((1, 15))
2662
return self._vfs_set_parent_location(url)
2664
raise errors.UnexpectedSmartServerResponse(response)
2666
def _vfs_set_parent_location(self, url):
2668
return self._real_branch._set_parent_location(url)
2671
def pull(self, source, overwrite=False, stop_revision=None,
2673
self._clear_cached_state_of_remote_branch_only()
2675
return self._real_branch.pull(
2676
source, overwrite=overwrite, stop_revision=stop_revision,
2677
_override_hook_target=self, **kwargs)
2680
def push(self, target, overwrite=False, stop_revision=None):
2682
return self._real_branch.push(
2683
target, overwrite=overwrite, stop_revision=stop_revision,
2684
_override_hook_source_branch=self)
2686
def is_locked(self):
2687
return self._lock_count >= 1
2690
def revision_id_to_revno(self, revision_id):
2692
return self._real_branch.revision_id_to_revno(revision_id)
2695
def set_last_revision_info(self, revno, revision_id):
2696
# XXX: These should be returned by the set_last_revision_info verb
2697
old_revno, old_revid = self.last_revision_info()
2698
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2699
revision_id = ensure_null(revision_id)
2701
response = self._call('Branch.set_last_revision_info',
2702
self._remote_path(), self._lock_token, self._repo_lock_token,
2703
str(revno), revision_id)
2704
except errors.UnknownSmartMethod:
2706
self._clear_cached_state_of_remote_branch_only()
2707
self._real_branch.set_last_revision_info(revno, revision_id)
2708
self._last_revision_info_cache = revno, revision_id
2710
if response == ('ok',):
2711
self._clear_cached_state()
2712
self._last_revision_info_cache = revno, revision_id
2713
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2714
# Update the _real_branch's cache too.
2715
if self._real_branch is not None:
2716
cache = self._last_revision_info_cache
2717
self._real_branch._last_revision_info_cache = cache
2719
raise errors.UnexpectedSmartServerResponse(response)
2722
def generate_revision_history(self, revision_id, last_rev=None,
2724
medium = self._client._medium
2725
if not medium._is_remote_before((1, 6)):
2726
# Use a smart method for 1.6 and above servers
2728
self._set_last_revision_descendant(revision_id, other_branch,
2729
allow_diverged=True, allow_overwrite_descendant=True)
2731
except errors.UnknownSmartMethod:
2732
medium._remember_remote_is_before((1, 6))
2733
self._clear_cached_state_of_remote_branch_only()
2734
self.set_revision_history(self._lefthand_history(revision_id,
2735
last_rev=last_rev,other_branch=other_branch))
2737
def set_push_location(self, location):
2739
return self._real_branch.set_push_location(location)
2742
class RemoteConfig(object):
2743
"""A Config that reads and writes from smart verbs.
2745
It is a low-level object that considers config data to be name/value pairs
2746
that may be associated with a section. Assigning meaning to the these
2747
values is done at higher levels like bzrlib.config.TreeConfig.
2750
def get_option(self, name, section=None, default=None):
2751
"""Return the value associated with a named option.
2753
:param name: The name of the value
2754
:param section: The section the option is in (if any)
2755
:param default: The value to return if the value is not set
2756
:return: The value or default value
2759
configobj = self._get_configobj()
2761
section_obj = configobj
2764
section_obj = configobj[section]
2767
return section_obj.get(name, default)
2768
except errors.UnknownSmartMethod:
2769
return self._vfs_get_option(name, section, default)
2771
def _response_to_configobj(self, response):
2772
if len(response[0]) and response[0][0] != 'ok':
2773
raise errors.UnexpectedSmartServerResponse(response)
2774
lines = response[1].read_body_bytes().splitlines()
2775
return config.ConfigObj(lines, encoding='utf-8')
2778
class RemoteBranchConfig(RemoteConfig):
2779
"""A RemoteConfig for Branches."""
2781
def __init__(self, branch):
2782
self._branch = branch
2784
def _get_configobj(self):
2785
path = self._branch._remote_path()
2786
response = self._branch._client.call_expecting_body(
2787
'Branch.get_config_file', path)
2788
return self._response_to_configobj(response)
2790
def set_option(self, value, name, section=None):
2791
"""Set the value associated with a named option.
2793
:param value: The value to set
2794
:param name: The name of the value to set
2795
:param section: The section the option is in (if any)
2797
medium = self._branch._client._medium
2798
if medium._is_remote_before((1, 14)):
2799
return self._vfs_set_option(value, name, section)
2800
if isinstance(value, dict):
2801
if medium._is_remote_before((2, 2)):
2802
return self._vfs_set_option(value, name, section)
2803
return self._set_config_option_dict(value, name, section)
2805
return self._set_config_option(value, name, section)
2807
def _set_config_option(self, value, name, section):
2809
path = self._branch._remote_path()
2810
response = self._branch._client.call('Branch.set_config_option',
2811
path, self._branch._lock_token, self._branch._repo_lock_token,
2812
value.encode('utf8'), name, section or '')
2813
except errors.UnknownSmartMethod:
2814
medium = self._branch._client._medium
2815
medium._remember_remote_is_before((1, 14))
2816
return self._vfs_set_option(value, name, section)
2818
raise errors.UnexpectedSmartServerResponse(response)
2820
def _serialize_option_dict(self, option_dict):
2822
for key, value in option_dict.items():
2823
if isinstance(key, unicode):
2824
key = key.encode('utf8')
2825
if isinstance(value, unicode):
2826
value = value.encode('utf8')
2827
utf8_dict[key] = value
2828
return bencode.bencode(utf8_dict)
2830
def _set_config_option_dict(self, value, name, section):
2832
path = self._branch._remote_path()
2833
serialised_dict = self._serialize_option_dict(value)
2834
response = self._branch._client.call(
2835
'Branch.set_config_option_dict',
2836
path, self._branch._lock_token, self._branch._repo_lock_token,
2837
serialised_dict, name, section or '')
2838
except errors.UnknownSmartMethod:
2839
medium = self._branch._client._medium
2840
medium._remember_remote_is_before((2, 2))
2841
return self._vfs_set_option(value, name, section)
2843
raise errors.UnexpectedSmartServerResponse(response)
2845
def _real_object(self):
2846
self._branch._ensure_real()
2847
return self._branch._real_branch
2849
def _vfs_set_option(self, value, name, section=None):
2850
return self._real_object()._get_config().set_option(
2851
value, name, section)
2854
class RemoteBzrDirConfig(RemoteConfig):
2855
"""A RemoteConfig for BzrDirs."""
2857
def __init__(self, bzrdir):
2858
self._bzrdir = bzrdir
2860
def _get_configobj(self):
2861
medium = self._bzrdir._client._medium
2862
verb = 'BzrDir.get_config_file'
2863
if medium._is_remote_before((1, 15)):
2864
raise errors.UnknownSmartMethod(verb)
2865
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2866
response = self._bzrdir._call_expecting_body(
2868
return self._response_to_configobj(response)
2870
def _vfs_get_option(self, name, section, default):
2871
return self._real_object()._get_config().get_option(
2872
name, section, default)
2874
def set_option(self, value, name, section=None):
2875
"""Set the value associated with a named option.
2877
:param value: The value to set
2878
:param name: The name of the value to set
2879
:param section: The section the option is in (if any)
2881
return self._real_object()._get_config().set_option(
2882
value, name, section)
2884
def _real_object(self):
2885
self._bzrdir._ensure_real()
2886
return self._bzrdir._real_bzrdir
2890
def _extract_tar(tar, to_dir):
2891
"""Extract all the contents of a tarfile object.
2893
A replacement for extractall, which is not present in python2.4
2896
tar.extract(tarinfo, to_dir)
2899
def _translate_error(err, **context):
2900
"""Translate an ErrorFromSmartServer into a more useful error.
2902
Possible context keys:
2910
If the error from the server doesn't match a known pattern, then
2911
UnknownErrorFromSmartServer is raised.
2915
return context[name]
2916
except KeyError, key_err:
2917
mutter('Missing key %r in context %r', key_err.args[0], context)
2920
"""Get the path from the context if present, otherwise use first error
2924
return context['path']
2925
except KeyError, key_err:
2927
return err.error_args[0]
2928
except IndexError, idx_err:
2930
'Missing key %r in context %r', key_err.args[0], context)
2933
if err.error_verb == 'IncompatibleRepositories':
2934
raise errors.IncompatibleRepositories(err.error_args[0],
2935
err.error_args[1], err.error_args[2])
2936
elif err.error_verb == 'NoSuchRevision':
2937
raise NoSuchRevision(find('branch'), err.error_args[0])
2938
elif err.error_verb == 'nosuchrevision':
2939
raise NoSuchRevision(find('repository'), err.error_args[0])
2940
elif err.error_verb == 'nobranch':
2941
if len(err.error_args) >= 1:
2942
extra = err.error_args[0]
2945
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2947
elif err.error_verb == 'norepository':
2948
raise errors.NoRepositoryPresent(find('bzrdir'))
2949
elif err.error_verb == 'LockContention':
2950
raise errors.LockContention('(remote lock)')
2951
elif err.error_verb == 'UnlockableTransport':
2952
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2953
elif err.error_verb == 'LockFailed':
2954
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2955
elif err.error_verb == 'TokenMismatch':
2956
raise errors.TokenMismatch(find('token'), '(remote token)')
2957
elif err.error_verb == 'Diverged':
2958
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2959
elif err.error_verb == 'TipChangeRejected':
2960
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2961
elif err.error_verb == 'UnstackableBranchFormat':
2962
raise errors.UnstackableBranchFormat(*err.error_args)
2963
elif err.error_verb == 'UnstackableRepositoryFormat':
2964
raise errors.UnstackableRepositoryFormat(*err.error_args)
2965
elif err.error_verb == 'NotStacked':
2966
raise errors.NotStacked(branch=find('branch'))
2967
elif err.error_verb == 'PermissionDenied':
2969
if len(err.error_args) >= 2:
2970
extra = err.error_args[1]
2973
raise errors.PermissionDenied(path, extra=extra)
2974
elif err.error_verb == 'ReadError':
2976
raise errors.ReadError(path)
2977
elif err.error_verb == 'NoSuchFile':
2979
raise errors.NoSuchFile(path)
2980
elif err.error_verb == 'FileExists':
2981
raise errors.FileExists(err.error_args[0])
2982
elif err.error_verb == 'DirectoryNotEmpty':
2983
raise errors.DirectoryNotEmpty(err.error_args[0])
2984
elif err.error_verb == 'ShortReadvError':
2985
args = err.error_args
2986
raise errors.ShortReadvError(
2987
args[0], int(args[1]), int(args[2]), int(args[3]))
2988
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2989
encoding = str(err.error_args[0]) # encoding must always be a string
2990
val = err.error_args[1]
2991
start = int(err.error_args[2])
2992
end = int(err.error_args[3])
2993
reason = str(err.error_args[4]) # reason must always be a string
2994
if val.startswith('u:'):
2995
val = val[2:].decode('utf-8')
2996
elif val.startswith('s:'):
2997
val = val[2:].decode('base64')
2998
if err.error_verb == 'UnicodeDecodeError':
2999
raise UnicodeDecodeError(encoding, val, start, end, reason)
3000
elif err.error_verb == 'UnicodeEncodeError':
3001
raise UnicodeEncodeError(encoding, val, start, end, reason)
3002
elif err.error_verb == 'ReadOnlyError':
3003
raise errors.TransportNotPossible('readonly transport')
3004
raise errors.UnknownErrorFromSmartServer(err)