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 = controldir.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
accelerator_tree=None, hardlink=False):
276
raise errors.NotLocalUrl(self.transport.base)
278
def find_branch_format(self, name=None):
279
"""Find the branch 'format' for this bzrdir.
281
This might be a synthetic object for e.g. RemoteBranch and SVN.
283
b = self.open_branch(name=name)
286
def get_branch_reference(self, name=None):
287
"""See BzrDir.get_branch_reference()."""
289
# XXX JRV20100304: Support opening colocated branches
290
raise errors.NoColocatedBranchSupport(self)
291
response = self._get_branch_reference()
292
if response[0] == 'ref':
297
def _get_branch_reference(self):
298
path = self._path_for_remote_call(self._client)
299
medium = self._client._medium
301
('BzrDir.open_branchV3', (2, 1)),
302
('BzrDir.open_branchV2', (1, 13)),
303
('BzrDir.open_branch', None),
305
for verb, required_version in candidate_calls:
306
if required_version and medium._is_remote_before(required_version):
309
response = self._call(verb, path)
310
except errors.UnknownSmartMethod:
311
if required_version is None:
313
medium._remember_remote_is_before(required_version)
316
if verb == 'BzrDir.open_branch':
317
if response[0] != 'ok':
318
raise errors.UnexpectedSmartServerResponse(response)
319
if response[1] != '':
320
return ('ref', response[1])
322
return ('branch', '')
323
if response[0] not in ('ref', 'branch'):
324
raise errors.UnexpectedSmartServerResponse(response)
327
def _get_tree_branch(self, name=None):
328
"""See BzrDir._get_tree_branch()."""
329
return None, self.open_branch(name=name)
331
def open_branch(self, name=None, unsupported=False,
332
ignore_fallbacks=False):
334
raise NotImplementedError('unsupported flag support not implemented yet.')
335
if self._next_open_branch_result is not None:
336
# See create_branch for details.
337
result = self._next_open_branch_result
338
self._next_open_branch_result = None
340
response = self._get_branch_reference()
341
if response[0] == 'ref':
342
# a branch reference, use the existing BranchReference logic.
343
format = BranchReferenceFormat()
344
return format.open(self, name=name, _found=True,
345
location=response[1], ignore_fallbacks=ignore_fallbacks)
346
branch_format_name = response[1]
347
if not branch_format_name:
348
branch_format_name = None
349
format = RemoteBranchFormat(network_name=branch_format_name)
350
return RemoteBranch(self, self.find_repository(), format=format,
351
setup_stacking=not ignore_fallbacks, name=name)
353
def _open_repo_v1(self, path):
354
verb = 'BzrDir.find_repository'
355
response = self._call(verb, path)
356
if response[0] != 'ok':
357
raise errors.UnexpectedSmartServerResponse(response)
358
# servers that only support the v1 method don't support external
361
repo = self._real_bzrdir.open_repository()
362
response = response + ('no', repo._format.network_name())
363
return response, repo
365
def _open_repo_v2(self, path):
366
verb = 'BzrDir.find_repositoryV2'
367
response = self._call(verb, path)
368
if response[0] != 'ok':
369
raise errors.UnexpectedSmartServerResponse(response)
371
repo = self._real_bzrdir.open_repository()
372
response = response + (repo._format.network_name(),)
373
return response, repo
375
def _open_repo_v3(self, path):
376
verb = 'BzrDir.find_repositoryV3'
377
medium = self._client._medium
378
if medium._is_remote_before((1, 13)):
379
raise errors.UnknownSmartMethod(verb)
381
response = self._call(verb, path)
382
except errors.UnknownSmartMethod:
383
medium._remember_remote_is_before((1, 13))
385
if response[0] != 'ok':
386
raise errors.UnexpectedSmartServerResponse(response)
387
return response, None
389
def open_repository(self):
390
path = self._path_for_remote_call(self._client)
392
for probe in [self._open_repo_v3, self._open_repo_v2,
395
response, real_repo = probe(path)
397
except errors.UnknownSmartMethod:
400
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
401
if response[0] != 'ok':
402
raise errors.UnexpectedSmartServerResponse(response)
403
if len(response) != 6:
404
raise SmartProtocolError('incorrect response length %s' % (response,))
405
if response[1] == '':
406
# repo is at this dir.
407
format = response_tuple_to_repo_format(response[2:])
408
# Used to support creating a real format instance when needed.
409
format._creating_bzrdir = self
410
remote_repo = RemoteRepository(self, format)
411
format._creating_repo = remote_repo
412
if real_repo is not None:
413
remote_repo._set_real_repository(real_repo)
416
raise errors.NoRepositoryPresent(self)
418
def has_workingtree(self):
419
if self._has_working_tree is None:
421
self._has_working_tree = self._real_bzrdir.has_workingtree()
422
return self._has_working_tree
424
def open_workingtree(self, recommend_upgrade=True):
425
if self.has_workingtree():
426
raise errors.NotLocalUrl(self.root_transport)
428
raise errors.NoWorkingTree(self.root_transport.base)
430
def _path_for_remote_call(self, client):
431
"""Return the path to be used for this bzrdir in a remote call."""
432
return client.remote_path_from_transport(self.root_transport)
434
def get_branch_transport(self, branch_format, name=None):
436
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
438
def get_repository_transport(self, repository_format):
440
return self._real_bzrdir.get_repository_transport(repository_format)
442
def get_workingtree_transport(self, workingtree_format):
444
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
446
def can_convert_format(self):
447
"""Upgrading of remote bzrdirs is not supported yet."""
450
def needs_format_conversion(self, format=None):
451
"""Upgrading of remote bzrdirs is not supported yet."""
453
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
454
% 'needs_format_conversion(format=None)')
457
def clone(self, url, revision_id=None, force_new_repo=False,
458
preserve_stacking=False):
460
return self._real_bzrdir.clone(url, revision_id=revision_id,
461
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
463
def _get_config(self):
464
return RemoteBzrDirConfig(self)
467
class RemoteRepositoryFormat(repository.RepositoryFormat):
468
"""Format for repositories accessed over a _SmartClient.
470
Instances of this repository are represented by RemoteRepository
473
The RemoteRepositoryFormat is parameterized during construction
474
to reflect the capabilities of the real, remote format. Specifically
475
the attributes rich_root_data and supports_tree_reference are set
476
on a per instance basis, and are not set (and should not be) at
479
:ivar _custom_format: If set, a specific concrete repository format that
480
will be used when initializing a repository with this
481
RemoteRepositoryFormat.
482
:ivar _creating_repo: If set, the repository object that this
483
RemoteRepositoryFormat was created for: it can be called into
484
to obtain data like the network name.
487
_matchingbzrdir = RemoteBzrDirFormat()
490
repository.RepositoryFormat.__init__(self)
491
self._custom_format = None
492
self._network_name = None
493
self._creating_bzrdir = None
494
self._supports_chks = None
495
self._supports_external_lookups = None
496
self._supports_tree_reference = None
497
self._rich_root_data = None
500
return "%s(_network_name=%r)" % (self.__class__.__name__,
504
def fast_deltas(self):
506
return self._custom_format.fast_deltas
509
def rich_root_data(self):
510
if self._rich_root_data is None:
512
self._rich_root_data = self._custom_format.rich_root_data
513
return self._rich_root_data
516
def supports_chks(self):
517
if self._supports_chks is None:
519
self._supports_chks = self._custom_format.supports_chks
520
return self._supports_chks
523
def supports_external_lookups(self):
524
if self._supports_external_lookups is None:
526
self._supports_external_lookups = \
527
self._custom_format.supports_external_lookups
528
return self._supports_external_lookups
531
def supports_tree_reference(self):
532
if self._supports_tree_reference is None:
534
self._supports_tree_reference = \
535
self._custom_format.supports_tree_reference
536
return self._supports_tree_reference
538
def _vfs_initialize(self, a_bzrdir, shared):
539
"""Helper for common code in initialize."""
540
if self._custom_format:
541
# Custom format requested
542
result = self._custom_format.initialize(a_bzrdir, shared=shared)
543
elif self._creating_bzrdir is not None:
544
# Use the format that the repository we were created to back
546
prior_repo = self._creating_bzrdir.open_repository()
547
prior_repo._ensure_real()
548
result = prior_repo._real_repository._format.initialize(
549
a_bzrdir, shared=shared)
551
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
552
# support remote initialization.
553
# We delegate to a real object at this point (as RemoteBzrDir
554
# delegate to the repository format which would lead to infinite
555
# recursion if we just called a_bzrdir.create_repository.
556
a_bzrdir._ensure_real()
557
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
558
if not isinstance(result, RemoteRepository):
559
return self.open(a_bzrdir)
563
def initialize(self, a_bzrdir, shared=False):
564
# Being asked to create on a non RemoteBzrDir:
565
if not isinstance(a_bzrdir, RemoteBzrDir):
566
return self._vfs_initialize(a_bzrdir, shared)
567
medium = a_bzrdir._client._medium
568
if medium._is_remote_before((1, 13)):
569
return self._vfs_initialize(a_bzrdir, shared)
570
# Creating on a remote bzr dir.
571
# 1) get the network name to use.
572
if self._custom_format:
573
network_name = self._custom_format.network_name()
574
elif self._network_name:
575
network_name = self._network_name
577
# Select the current bzrlib default and ask for that.
578
reference_bzrdir_format = bzrdir.format_registry.get('default')()
579
reference_format = reference_bzrdir_format.repository_format
580
network_name = reference_format.network_name()
581
# 2) try direct creation via RPC
582
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
583
verb = 'BzrDir.create_repository'
589
response = a_bzrdir._call(verb, path, network_name, shared_str)
590
except errors.UnknownSmartMethod:
591
# Fallback - use vfs methods
592
medium._remember_remote_is_before((1, 13))
593
return self._vfs_initialize(a_bzrdir, shared)
595
# Turn the response into a RemoteRepository object.
596
format = response_tuple_to_repo_format(response[1:])
597
# Used to support creating a real format instance when needed.
598
format._creating_bzrdir = a_bzrdir
599
remote_repo = RemoteRepository(a_bzrdir, format)
600
format._creating_repo = remote_repo
603
def open(self, a_bzrdir):
604
if not isinstance(a_bzrdir, RemoteBzrDir):
605
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
606
return a_bzrdir.open_repository()
608
def _ensure_real(self):
609
if self._custom_format is None:
610
self._custom_format = repository.network_format_registry.get(
614
def _fetch_order(self):
616
return self._custom_format._fetch_order
619
def _fetch_uses_deltas(self):
621
return self._custom_format._fetch_uses_deltas
624
def _fetch_reconcile(self):
626
return self._custom_format._fetch_reconcile
628
def get_format_description(self):
630
return 'Remote: ' + self._custom_format.get_format_description()
632
def __eq__(self, other):
633
return self.__class__ is other.__class__
635
def network_name(self):
636
if self._network_name:
637
return self._network_name
638
self._creating_repo._ensure_real()
639
return self._creating_repo._real_repository._format.network_name()
642
def pack_compresses(self):
644
return self._custom_format.pack_compresses
647
def _serializer(self):
649
return self._custom_format._serializer
652
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
653
controldir.ControlComponent):
654
"""Repository accessed over rpc.
656
For the moment most operations are performed using local transport-backed
660
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
661
"""Create a RemoteRepository instance.
663
:param remote_bzrdir: The bzrdir hosting this repository.
664
:param format: The RemoteFormat object to use.
665
:param real_repository: If not None, a local implementation of the
666
repository logic for the repository, usually accessing the data
668
:param _client: Private testing parameter - override the smart client
669
to be used by the repository.
672
self._real_repository = real_repository
674
self._real_repository = None
675
self.bzrdir = remote_bzrdir
677
self._client = remote_bzrdir._client
679
self._client = _client
680
self._format = format
681
self._lock_mode = None
682
self._lock_token = None
684
self._leave_lock = False
685
# Cache of revision parents; misses are cached during read locks, and
686
# write locks when no _real_repository has been set.
687
self._unstacked_provider = graph.CachingParentsProvider(
688
get_parent_map=self._get_parent_map_rpc)
689
self._unstacked_provider.disable_cache()
691
# These depend on the actual remote format, so force them off for
692
# maximum compatibility. XXX: In future these should depend on the
693
# remote repository instance, but this is irrelevant until we perform
694
# reconcile via an RPC call.
695
self._reconcile_does_inventory_gc = False
696
self._reconcile_fixes_text_parents = False
697
self._reconcile_backsup_inventory = False
698
self.base = self.bzrdir.transport.base
699
# Additional places to query for data.
700
self._fallback_repositories = []
703
def user_transport(self):
704
return self.bzrdir.user_transport
707
def control_transport(self):
708
# XXX: Normally you shouldn't directly get at the remote repository
709
# transport, but I'm not sure it's worth making this method
710
# optional -- mbp 2010-04-21
711
return self.bzrdir.get_repository_transport(None)
714
return "%s(%s)" % (self.__class__.__name__, self.base)
718
def abort_write_group(self, suppress_errors=False):
719
"""Complete a write group on the decorated repository.
721
Smart methods perform operations in a single step so this API
722
is not really applicable except as a compatibility thunk
723
for older plugins that don't use e.g. the CommitBuilder
726
:param suppress_errors: see Repository.abort_write_group.
729
return self._real_repository.abort_write_group(
730
suppress_errors=suppress_errors)
734
"""Decorate the real repository for now.
736
In the long term a full blown network facility is needed to avoid
737
creating a real repository object locally.
740
return self._real_repository.chk_bytes
742
def commit_write_group(self):
743
"""Complete a write group on the decorated repository.
745
Smart methods perform operations in a single step so this API
746
is not really applicable except as a compatibility thunk
747
for older plugins that don't use e.g. the CommitBuilder
751
return self._real_repository.commit_write_group()
753
def resume_write_group(self, tokens):
755
return self._real_repository.resume_write_group(tokens)
757
def suspend_write_group(self):
759
return self._real_repository.suspend_write_group()
761
def get_missing_parent_inventories(self, check_for_missing_texts=True):
763
return self._real_repository.get_missing_parent_inventories(
764
check_for_missing_texts=check_for_missing_texts)
766
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
768
return self._real_repository.get_rev_id_for_revno(
771
def get_rev_id_for_revno(self, revno, known_pair):
772
"""See Repository.get_rev_id_for_revno."""
773
path = self.bzrdir._path_for_remote_call(self._client)
775
if self._client._medium._is_remote_before((1, 17)):
776
return self._get_rev_id_for_revno_vfs(revno, known_pair)
777
response = self._call(
778
'Repository.get_rev_id_for_revno', path, revno, known_pair)
779
except errors.UnknownSmartMethod:
780
self._client._medium._remember_remote_is_before((1, 17))
781
return self._get_rev_id_for_revno_vfs(revno, known_pair)
782
if response[0] == 'ok':
783
return True, response[1]
784
elif response[0] == 'history-incomplete':
785
known_pair = response[1:3]
786
for fallback in self._fallback_repositories:
787
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
792
# Not found in any fallbacks
793
return False, known_pair
795
raise errors.UnexpectedSmartServerResponse(response)
797
def _ensure_real(self):
798
"""Ensure that there is a _real_repository set.
800
Used before calls to self._real_repository.
802
Note that _ensure_real causes many roundtrips to the server which are
803
not desirable, and prevents the use of smart one-roundtrip RPC's to
804
perform complex operations (such as accessing parent data, streaming
805
revisions etc). Adding calls to _ensure_real should only be done when
806
bringing up new functionality, adding fallbacks for smart methods that
807
require a fallback path, and never to replace an existing smart method
808
invocation. If in doubt chat to the bzr network team.
810
if self._real_repository is None:
811
if 'hpssvfs' in debug.debug_flags:
813
warning('VFS Repository access triggered\n%s',
814
''.join(traceback.format_stack()))
815
self._unstacked_provider.missing_keys.clear()
816
self.bzrdir._ensure_real()
817
self._set_real_repository(
818
self.bzrdir._real_bzrdir.open_repository())
820
def _translate_error(self, err, **context):
821
self.bzrdir._translate_error(err, repository=self, **context)
823
def find_text_key_references(self):
824
"""Find the text key references within the repository.
826
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
827
revision_ids. Each altered file-ids has the exact revision_ids that
828
altered it listed explicitly.
829
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
830
to whether they were referred to by the inventory of the
831
revision_id that they contain. The inventory texts from all present
832
revision ids are assessed to generate this report.
835
return self._real_repository.find_text_key_references()
837
def _generate_text_key_index(self):
838
"""Generate a new text key index for the repository.
840
This is an expensive function that will take considerable time to run.
842
:return: A dict mapping (file_id, revision_id) tuples to a list of
843
parents, also (file_id, revision_id) tuples.
846
return self._real_repository._generate_text_key_index()
848
def _get_revision_graph(self, revision_id):
849
"""Private method for using with old (< 1.2) servers to fallback."""
850
if revision_id is None:
852
elif revision.is_null(revision_id):
855
path = self.bzrdir._path_for_remote_call(self._client)
856
response = self._call_expecting_body(
857
'Repository.get_revision_graph', path, revision_id)
858
response_tuple, response_handler = response
859
if response_tuple[0] != 'ok':
860
raise errors.UnexpectedSmartServerResponse(response_tuple)
861
coded = response_handler.read_body_bytes()
863
# no revisions in this repository!
865
lines = coded.split('\n')
868
d = tuple(line.split())
869
revision_graph[d[0]] = d[1:]
871
return revision_graph
874
"""See Repository._get_sink()."""
875
return RemoteStreamSink(self)
877
def _get_source(self, to_format):
878
"""Return a source for streaming from this repository."""
879
return RemoteStreamSource(self, to_format)
882
def has_revision(self, revision_id):
883
"""True if this repository has a copy of the revision."""
884
# Copy of bzrlib.repository.Repository.has_revision
885
return revision_id in self.has_revisions((revision_id,))
888
def has_revisions(self, revision_ids):
889
"""Probe to find out the presence of multiple revisions.
891
:param revision_ids: An iterable of revision_ids.
892
:return: A set of the revision_ids that were present.
894
# Copy of bzrlib.repository.Repository.has_revisions
895
parent_map = self.get_parent_map(revision_ids)
896
result = set(parent_map)
897
if _mod_revision.NULL_REVISION in revision_ids:
898
result.add(_mod_revision.NULL_REVISION)
901
def _has_same_fallbacks(self, other_repo):
902
"""Returns true if the repositories have the same fallbacks."""
903
# XXX: copied from Repository; it should be unified into a base class
904
# <https://bugs.launchpad.net/bzr/+bug/401622>
905
my_fb = self._fallback_repositories
906
other_fb = other_repo._fallback_repositories
907
if len(my_fb) != len(other_fb):
909
for f, g in zip(my_fb, other_fb):
910
if not f.has_same_location(g):
914
def has_same_location(self, other):
915
# TODO: Move to RepositoryBase and unify with the regular Repository
916
# one; unfortunately the tests rely on slightly different behaviour at
917
# present -- mbp 20090710
918
return (self.__class__ is other.__class__ and
919
self.bzrdir.transport.base == other.bzrdir.transport.base)
921
def get_graph(self, other_repository=None):
922
"""Return the graph for this repository format"""
923
parents_provider = self._make_parents_provider(other_repository)
924
return graph.Graph(parents_provider)
927
def get_known_graph_ancestry(self, revision_ids):
928
"""Return the known graph for a set of revision ids and their ancestors.
930
st = static_tuple.StaticTuple
931
revision_keys = [st(r_id).intern() for r_id in revision_ids]
932
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
933
return graph.GraphThunkIdsToKeys(known_graph)
935
def gather_stats(self, revid=None, committers=None):
936
"""See Repository.gather_stats()."""
937
path = self.bzrdir._path_for_remote_call(self._client)
938
# revid can be None to indicate no revisions, not just NULL_REVISION
939
if revid is None or revision.is_null(revid):
943
if committers is None or not committers:
944
fmt_committers = 'no'
946
fmt_committers = 'yes'
947
response_tuple, response_handler = self._call_expecting_body(
948
'Repository.gather_stats', path, fmt_revid, fmt_committers)
949
if response_tuple[0] != 'ok':
950
raise errors.UnexpectedSmartServerResponse(response_tuple)
952
body = response_handler.read_body_bytes()
954
for line in body.split('\n'):
957
key, val_text = line.split(':')
958
if key in ('revisions', 'size', 'committers'):
959
result[key] = int(val_text)
960
elif key in ('firstrev', 'latestrev'):
961
values = val_text.split(' ')[1:]
962
result[key] = (float(values[0]), long(values[1]))
966
def find_branches(self, using=False):
967
"""See Repository.find_branches()."""
968
# should be an API call to the server.
970
return self._real_repository.find_branches(using=using)
972
def get_physical_lock_status(self):
973
"""See Repository.get_physical_lock_status()."""
974
# should be an API call to the server.
976
return self._real_repository.get_physical_lock_status()
978
def is_in_write_group(self):
979
"""Return True if there is an open write group.
981
write groups are only applicable locally for the smart server..
983
if self._real_repository:
984
return self._real_repository.is_in_write_group()
987
return self._lock_count >= 1
990
"""See Repository.is_shared()."""
991
path = self.bzrdir._path_for_remote_call(self._client)
992
response = self._call('Repository.is_shared', path)
993
if response[0] not in ('yes', 'no'):
994
raise SmartProtocolError('unexpected response code %s' % (response,))
995
return response[0] == 'yes'
997
def is_write_locked(self):
998
return self._lock_mode == 'w'
1000
def _warn_if_deprecated(self, branch=None):
1001
# If we have a real repository, the check will be done there, if we
1002
# don't the check will be done remotely.
1005
def lock_read(self):
1006
"""Lock the repository for read operations.
1008
:return: A bzrlib.lock.LogicalLockResult.
1010
# wrong eventually - want a local lock cache context
1011
if not self._lock_mode:
1012
self._note_lock('r')
1013
self._lock_mode = 'r'
1014
self._lock_count = 1
1015
self._unstacked_provider.enable_cache(cache_misses=True)
1016
if self._real_repository is not None:
1017
self._real_repository.lock_read()
1018
for repo in self._fallback_repositories:
1021
self._lock_count += 1
1022
return lock.LogicalLockResult(self.unlock)
1024
def _remote_lock_write(self, token):
1025
path = self.bzrdir._path_for_remote_call(self._client)
1028
err_context = {'token': token}
1029
response = self._call('Repository.lock_write', path, token,
1031
if response[0] == 'ok':
1032
ok, token = response
1035
raise errors.UnexpectedSmartServerResponse(response)
1037
def lock_write(self, token=None, _skip_rpc=False):
1038
if not self._lock_mode:
1039
self._note_lock('w')
1041
if self._lock_token is not None:
1042
if token != self._lock_token:
1043
raise errors.TokenMismatch(token, self._lock_token)
1044
self._lock_token = token
1046
self._lock_token = self._remote_lock_write(token)
1047
# if self._lock_token is None, then this is something like packs or
1048
# svn where we don't get to lock the repo, or a weave style repository
1049
# where we cannot lock it over the wire and attempts to do so will
1051
if self._real_repository is not None:
1052
self._real_repository.lock_write(token=self._lock_token)
1053
if token is not None:
1054
self._leave_lock = True
1056
self._leave_lock = False
1057
self._lock_mode = 'w'
1058
self._lock_count = 1
1059
cache_misses = self._real_repository is None
1060
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1061
for repo in self._fallback_repositories:
1062
# Writes don't affect fallback repos
1064
elif self._lock_mode == 'r':
1065
raise errors.ReadOnlyError(self)
1067
self._lock_count += 1
1068
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1070
def leave_lock_in_place(self):
1071
if not self._lock_token:
1072
raise NotImplementedError(self.leave_lock_in_place)
1073
self._leave_lock = True
1075
def dont_leave_lock_in_place(self):
1076
if not self._lock_token:
1077
raise NotImplementedError(self.dont_leave_lock_in_place)
1078
self._leave_lock = False
1080
def _set_real_repository(self, repository):
1081
"""Set the _real_repository for this repository.
1083
:param repository: The repository to fallback to for non-hpss
1084
implemented operations.
1086
if self._real_repository is not None:
1087
# Replacing an already set real repository.
1088
# We cannot do this [currently] if the repository is locked -
1089
# synchronised state might be lost.
1090
if self.is_locked():
1091
raise AssertionError('_real_repository is already set')
1092
if isinstance(repository, RemoteRepository):
1093
raise AssertionError()
1094
self._real_repository = repository
1095
# three code paths happen here:
1096
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1097
# up stacking. In this case self._fallback_repositories is [], and the
1098
# real repo is already setup. Preserve the real repo and
1099
# RemoteRepository.add_fallback_repository will avoid adding
1101
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1102
# ensure_real is triggered from a branch, the real repository to
1103
# set already has a matching list with separate instances, but
1104
# as they are also RemoteRepositories we don't worry about making the
1105
# lists be identical.
1106
# 3) new servers, RemoteRepository.ensure_real is triggered before
1107
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1108
# and need to populate it.
1109
if (self._fallback_repositories and
1110
len(self._real_repository._fallback_repositories) !=
1111
len(self._fallback_repositories)):
1112
if len(self._real_repository._fallback_repositories):
1113
raise AssertionError(
1114
"cannot cleanly remove existing _fallback_repositories")
1115
for fb in self._fallback_repositories:
1116
self._real_repository.add_fallback_repository(fb)
1117
if self._lock_mode == 'w':
1118
# if we are already locked, the real repository must be able to
1119
# acquire the lock with our token.
1120
self._real_repository.lock_write(self._lock_token)
1121
elif self._lock_mode == 'r':
1122
self._real_repository.lock_read()
1124
def start_write_group(self):
1125
"""Start a write group on the decorated repository.
1127
Smart methods perform operations in a single step so this API
1128
is not really applicable except as a compatibility thunk
1129
for older plugins that don't use e.g. the CommitBuilder
1133
return self._real_repository.start_write_group()
1135
def _unlock(self, token):
1136
path = self.bzrdir._path_for_remote_call(self._client)
1138
# with no token the remote repository is not persistently locked.
1140
err_context = {'token': token}
1141
response = self._call('Repository.unlock', path, token,
1143
if response == ('ok',):
1146
raise errors.UnexpectedSmartServerResponse(response)
1148
@only_raises(errors.LockNotHeld, errors.LockBroken)
1150
if not self._lock_count:
1151
return lock.cant_unlock_not_held(self)
1152
self._lock_count -= 1
1153
if self._lock_count > 0:
1155
self._unstacked_provider.disable_cache()
1156
old_mode = self._lock_mode
1157
self._lock_mode = None
1159
# The real repository is responsible at present for raising an
1160
# exception if it's in an unfinished write group. However, it
1161
# normally will *not* actually remove the lock from disk - that's
1162
# done by the server on receiving the Repository.unlock call.
1163
# This is just to let the _real_repository stay up to date.
1164
if self._real_repository is not None:
1165
self._real_repository.unlock()
1167
# The rpc-level lock should be released even if there was a
1168
# problem releasing the vfs-based lock.
1170
# Only write-locked repositories need to make a remote method
1171
# call to perform the unlock.
1172
old_token = self._lock_token
1173
self._lock_token = None
1174
if not self._leave_lock:
1175
self._unlock(old_token)
1176
# Fallbacks are always 'lock_read()' so we don't pay attention to
1178
for repo in self._fallback_repositories:
1181
def break_lock(self):
1182
# should hand off to the network
1184
return self._real_repository.break_lock()
1186
def _get_tarball(self, compression):
1187
"""Return a TemporaryFile containing a repository tarball.
1189
Returns None if the server does not support sending tarballs.
1192
path = self.bzrdir._path_for_remote_call(self._client)
1194
response, protocol = self._call_expecting_body(
1195
'Repository.tarball', path, compression)
1196
except errors.UnknownSmartMethod:
1197
protocol.cancel_read_body()
1199
if response[0] == 'ok':
1200
# Extract the tarball and return it
1201
t = tempfile.NamedTemporaryFile()
1202
# TODO: rpc layer should read directly into it...
1203
t.write(protocol.read_body_bytes())
1206
raise errors.UnexpectedSmartServerResponse(response)
1208
def sprout(self, to_bzrdir, revision_id=None):
1209
# TODO: Option to control what format is created?
1211
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1213
dest_repo.fetch(self, revision_id=revision_id)
1216
### These methods are just thin shims to the VFS object for now.
1218
def revision_tree(self, revision_id):
1220
return self._real_repository.revision_tree(revision_id)
1222
def get_serializer_format(self):
1224
return self._real_repository.get_serializer_format()
1226
def get_commit_builder(self, branch, parents, config, timestamp=None,
1227
timezone=None, committer=None, revprops=None,
1229
# FIXME: It ought to be possible to call this without immediately
1230
# triggering _ensure_real. For now it's the easiest thing to do.
1232
real_repo = self._real_repository
1233
builder = real_repo.get_commit_builder(branch, parents,
1234
config, timestamp=timestamp, timezone=timezone,
1235
committer=committer, revprops=revprops, revision_id=revision_id)
1238
def add_fallback_repository(self, repository):
1239
"""Add a repository to use for looking up data not held locally.
1241
:param repository: A repository.
1243
if not self._format.supports_external_lookups:
1244
raise errors.UnstackableRepositoryFormat(
1245
self._format.network_name(), self.base)
1246
# We need to accumulate additional repositories here, to pass them in
1249
if self.is_locked():
1250
# We will call fallback.unlock() when we transition to the unlocked
1251
# state, so always add a lock here. If a caller passes us a locked
1252
# repository, they are responsible for unlocking it later.
1253
repository.lock_read()
1254
self._check_fallback_repository(repository)
1255
self._fallback_repositories.append(repository)
1256
# If self._real_repository was parameterised already (e.g. because a
1257
# _real_branch had its get_stacked_on_url method called), then the
1258
# repository to be added may already be in the _real_repositories list.
1259
if self._real_repository is not None:
1260
fallback_locations = [repo.user_url for repo in
1261
self._real_repository._fallback_repositories]
1262
if repository.user_url not in fallback_locations:
1263
self._real_repository.add_fallback_repository(repository)
1265
def _check_fallback_repository(self, repository):
1266
"""Check that this repository can fallback to repository safely.
1268
Raise an error if not.
1270
:param repository: A repository to fallback to.
1272
return _mod_repository.InterRepository._assert_same_model(
1275
def add_inventory(self, revid, inv, parents):
1277
return self._real_repository.add_inventory(revid, inv, parents)
1279
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1280
parents, basis_inv=None, propagate_caches=False):
1282
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1283
delta, new_revision_id, parents, basis_inv=basis_inv,
1284
propagate_caches=propagate_caches)
1286
def add_revision(self, rev_id, rev, inv=None, config=None):
1288
return self._real_repository.add_revision(
1289
rev_id, rev, inv=inv, config=config)
1292
def get_inventory(self, revision_id):
1294
return self._real_repository.get_inventory(revision_id)
1296
def iter_inventories(self, revision_ids, ordering=None):
1298
return self._real_repository.iter_inventories(revision_ids, ordering)
1301
def get_revision(self, revision_id):
1303
return self._real_repository.get_revision(revision_id)
1305
def get_transaction(self):
1307
return self._real_repository.get_transaction()
1310
def clone(self, a_bzrdir, revision_id=None):
1312
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1314
def make_working_trees(self):
1315
"""See Repository.make_working_trees"""
1317
return self._real_repository.make_working_trees()
1319
def refresh_data(self):
1320
"""Re-read any data needed to synchronise with disk.
1322
This method is intended to be called after another repository instance
1323
(such as one used by a smart server) has inserted data into the
1324
repository. On all repositories this will work outside of write groups.
1325
Some repository formats (pack and newer for bzrlib native formats)
1326
support refresh_data inside write groups. If called inside a write
1327
group on a repository that does not support refreshing in a write group
1328
IsInWriteGroupError will be raised.
1330
if self._real_repository is not None:
1331
self._real_repository.refresh_data()
1333
def revision_ids_to_search_result(self, result_set):
1334
"""Convert a set of revision ids to a graph SearchResult."""
1335
result_parents = set()
1336
for parents in self.get_graph().get_parent_map(
1337
result_set).itervalues():
1338
result_parents.update(parents)
1339
included_keys = result_set.intersection(result_parents)
1340
start_keys = result_set.difference(included_keys)
1341
exclude_keys = result_parents.difference(result_set)
1342
result = graph.SearchResult(start_keys, exclude_keys,
1343
len(result_set), result_set)
1347
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1348
"""Return the revision ids that other has that this does not.
1350
These are returned in topological order.
1352
revision_id: only return revision ids included by revision_id.
1354
return repository.InterRepository.get(
1355
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1357
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1359
# No base implementation to use as RemoteRepository is not a subclass
1360
# of Repository; so this is a copy of Repository.fetch().
1361
if fetch_spec is not None and revision_id is not None:
1362
raise AssertionError(
1363
"fetch_spec and revision_id are mutually exclusive.")
1364
if self.is_in_write_group():
1365
raise errors.InternalBzrError(
1366
"May not fetch while in a write group.")
1367
# fast path same-url fetch operations
1368
if (self.has_same_location(source)
1369
and fetch_spec is None
1370
and self._has_same_fallbacks(source)):
1371
# check that last_revision is in 'from' and then return a
1373
if (revision_id is not None and
1374
not revision.is_null(revision_id)):
1375
self.get_revision(revision_id)
1377
# if there is no specific appropriate InterRepository, this will get
1378
# the InterRepository base class, which raises an
1379
# IncompatibleRepositories when asked to fetch.
1380
inter = repository.InterRepository.get(source, self)
1381
return inter.fetch(revision_id=revision_id, pb=pb,
1382
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1384
def create_bundle(self, target, base, fileobj, format=None):
1386
self._real_repository.create_bundle(target, base, fileobj, format)
1389
def get_ancestry(self, revision_id, topo_sorted=True):
1391
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1393
def fileids_altered_by_revision_ids(self, revision_ids):
1395
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1397
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1399
return self._real_repository._get_versioned_file_checker(
1400
revisions, revision_versions_cache)
1402
def iter_files_bytes(self, desired_files):
1403
"""See Repository.iter_file_bytes.
1406
return self._real_repository.iter_files_bytes(desired_files)
1408
def get_parent_map(self, revision_ids):
1409
"""See bzrlib.Graph.get_parent_map()."""
1410
return self._make_parents_provider().get_parent_map(revision_ids)
1412
def _get_parent_map_rpc(self, keys):
1413
"""Helper for get_parent_map that performs the RPC."""
1414
medium = self._client._medium
1415
if medium._is_remote_before((1, 2)):
1416
# We already found out that the server can't understand
1417
# Repository.get_parent_map requests, so just fetch the whole
1420
# Note that this reads the whole graph, when only some keys are
1421
# wanted. On this old server there's no way (?) to get them all
1422
# in one go, and the user probably will have seen a warning about
1423
# the server being old anyhow.
1424
rg = self._get_revision_graph(None)
1425
# There is an API discrepancy between get_parent_map and
1426
# get_revision_graph. Specifically, a "key:()" pair in
1427
# get_revision_graph just means a node has no parents. For
1428
# "get_parent_map" it means the node is a ghost. So fix up the
1429
# graph to correct this.
1430
# https://bugs.launchpad.net/bzr/+bug/214894
1431
# There is one other "bug" which is that ghosts in
1432
# get_revision_graph() are not returned at all. But we won't worry
1433
# about that for now.
1434
for node_id, parent_ids in rg.iteritems():
1435
if parent_ids == ():
1436
rg[node_id] = (NULL_REVISION,)
1437
rg[NULL_REVISION] = ()
1442
raise ValueError('get_parent_map(None) is not valid')
1443
if NULL_REVISION in keys:
1444
keys.discard(NULL_REVISION)
1445
found_parents = {NULL_REVISION:()}
1447
return found_parents
1450
# TODO(Needs analysis): We could assume that the keys being requested
1451
# from get_parent_map are in a breadth first search, so typically they
1452
# will all be depth N from some common parent, and we don't have to
1453
# have the server iterate from the root parent, but rather from the
1454
# keys we're searching; and just tell the server the keyspace we
1455
# already have; but this may be more traffic again.
1457
# Transform self._parents_map into a search request recipe.
1458
# TODO: Manage this incrementally to avoid covering the same path
1459
# repeatedly. (The server will have to on each request, but the less
1460
# work done the better).
1462
# Negative caching notes:
1463
# new server sends missing when a request including the revid
1464
# 'include-missing:' is present in the request.
1465
# missing keys are serialised as missing:X, and we then call
1466
# provider.note_missing(X) for-all X
1467
parents_map = self._unstacked_provider.get_cached_map()
1468
if parents_map is None:
1469
# Repository is not locked, so there's no cache.
1471
# start_set is all the keys in the cache
1472
start_set = set(parents_map)
1473
# result set is all the references to keys in the cache
1474
result_parents = set()
1475
for parents in parents_map.itervalues():
1476
result_parents.update(parents)
1477
stop_keys = result_parents.difference(start_set)
1478
# We don't need to send ghosts back to the server as a position to
1480
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1481
key_count = len(parents_map)
1482
if (NULL_REVISION in result_parents
1483
and NULL_REVISION in self._unstacked_provider.missing_keys):
1484
# If we pruned NULL_REVISION from the stop_keys because it's also
1485
# in our cache of "missing" keys we need to increment our key count
1486
# by 1, because the reconsitituted SearchResult on the server will
1487
# still consider NULL_REVISION to be an included key.
1489
included_keys = start_set.intersection(result_parents)
1490
start_set.difference_update(included_keys)
1491
recipe = ('manual', start_set, stop_keys, key_count)
1492
body = self._serialise_search_recipe(recipe)
1493
path = self.bzrdir._path_for_remote_call(self._client)
1495
if type(key) is not str:
1497
"key %r not a plain string" % (key,))
1498
verb = 'Repository.get_parent_map'
1499
args = (path, 'include-missing:') + tuple(keys)
1501
response = self._call_with_body_bytes_expecting_body(
1503
except errors.UnknownSmartMethod:
1504
# Server does not support this method, so get the whole graph.
1505
# Worse, we have to force a disconnection, because the server now
1506
# doesn't realise it has a body on the wire to consume, so the
1507
# only way to recover is to abandon the connection.
1509
'Server is too old for fast get_parent_map, reconnecting. '
1510
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1512
# To avoid having to disconnect repeatedly, we keep track of the
1513
# fact the server doesn't understand remote methods added in 1.2.
1514
medium._remember_remote_is_before((1, 2))
1515
# Recurse just once and we should use the fallback code.
1516
return self._get_parent_map_rpc(keys)
1517
response_tuple, response_handler = response
1518
if response_tuple[0] not in ['ok']:
1519
response_handler.cancel_read_body()
1520
raise errors.UnexpectedSmartServerResponse(response_tuple)
1521
if response_tuple[0] == 'ok':
1522
coded = bz2.decompress(response_handler.read_body_bytes())
1524
# no revisions found
1526
lines = coded.split('\n')
1529
d = tuple(line.split())
1531
revision_graph[d[0]] = d[1:]
1534
if d[0].startswith('missing:'):
1536
self._unstacked_provider.note_missing_key(revid)
1538
# no parents - so give the Graph result
1540
revision_graph[d[0]] = (NULL_REVISION,)
1541
return revision_graph
1544
def get_signature_text(self, revision_id):
1546
return self._real_repository.get_signature_text(revision_id)
1549
def _get_inventory_xml(self, revision_id):
1551
return self._real_repository._get_inventory_xml(revision_id)
1553
def reconcile(self, other=None, thorough=False):
1555
return self._real_repository.reconcile(other=other, thorough=thorough)
1557
def all_revision_ids(self):
1559
return self._real_repository.all_revision_ids()
1562
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1564
return self._real_repository.get_deltas_for_revisions(revisions,
1565
specific_fileids=specific_fileids)
1568
def get_revision_delta(self, revision_id, specific_fileids=None):
1570
return self._real_repository.get_revision_delta(revision_id,
1571
specific_fileids=specific_fileids)
1574
def revision_trees(self, revision_ids):
1576
return self._real_repository.revision_trees(revision_ids)
1579
def get_revision_reconcile(self, revision_id):
1581
return self._real_repository.get_revision_reconcile(revision_id)
1584
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1586
return self._real_repository.check(revision_ids=revision_ids,
1587
callback_refs=callback_refs, check_repo=check_repo)
1589
def copy_content_into(self, destination, revision_id=None):
1591
return self._real_repository.copy_content_into(
1592
destination, revision_id=revision_id)
1594
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1595
# get a tarball of the remote repository, and copy from that into the
1597
from bzrlib import osutils
1599
# TODO: Maybe a progress bar while streaming the tarball?
1600
note("Copying repository content as tarball...")
1601
tar_file = self._get_tarball('bz2')
1602
if tar_file is None:
1604
destination = to_bzrdir.create_repository()
1606
tar = tarfile.open('repository', fileobj=tar_file,
1608
tmpdir = osutils.mkdtemp()
1610
_extract_tar(tar, tmpdir)
1611
tmp_bzrdir = BzrDir.open(tmpdir)
1612
tmp_repo = tmp_bzrdir.open_repository()
1613
tmp_repo.copy_content_into(destination, revision_id)
1615
osutils.rmtree(tmpdir)
1619
# TODO: Suggestion from john: using external tar is much faster than
1620
# python's tarfile library, but it may not work on windows.
1623
def inventories(self):
1624
"""Decorate the real repository for now.
1626
In the long term a full blown network facility is needed to
1627
avoid creating a real repository object locally.
1630
return self._real_repository.inventories
1633
def pack(self, hint=None, clean_obsolete_packs=False):
1634
"""Compress the data within the repository.
1636
This is not currently implemented within the smart server.
1639
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1642
def revisions(self):
1643
"""Decorate the real repository for now.
1645
In the short term this should become a real object to intercept graph
1648
In the long term a full blown network facility is needed.
1651
return self._real_repository.revisions
1653
def set_make_working_trees(self, new_value):
1655
new_value_str = "True"
1657
new_value_str = "False"
1658
path = self.bzrdir._path_for_remote_call(self._client)
1660
response = self._call(
1661
'Repository.set_make_working_trees', path, new_value_str)
1662
except errors.UnknownSmartMethod:
1664
self._real_repository.set_make_working_trees(new_value)
1666
if response[0] != 'ok':
1667
raise errors.UnexpectedSmartServerResponse(response)
1670
def signatures(self):
1671
"""Decorate the real repository for now.
1673
In the long term a full blown network facility is needed to avoid
1674
creating a real repository object locally.
1677
return self._real_repository.signatures
1680
def sign_revision(self, revision_id, gpg_strategy):
1682
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1686
"""Decorate the real repository for now.
1688
In the long term a full blown network facility is needed to avoid
1689
creating a real repository object locally.
1692
return self._real_repository.texts
1695
def get_revisions(self, revision_ids):
1697
return self._real_repository.get_revisions(revision_ids)
1699
def supports_rich_root(self):
1700
return self._format.rich_root_data
1702
def iter_reverse_revision_history(self, revision_id):
1704
return self._real_repository.iter_reverse_revision_history(revision_id)
1707
def _serializer(self):
1708
return self._format._serializer
1710
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1712
return self._real_repository.store_revision_signature(
1713
gpg_strategy, plaintext, revision_id)
1715
def add_signature_text(self, revision_id, signature):
1717
return self._real_repository.add_signature_text(revision_id, signature)
1719
def has_signature_for_revision_id(self, revision_id):
1721
return self._real_repository.has_signature_for_revision_id(revision_id)
1723
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1725
return self._real_repository.item_keys_introduced_by(revision_ids,
1726
_files_pb=_files_pb)
1728
def revision_graph_can_have_wrong_parents(self):
1729
# The answer depends on the remote repo format.
1731
return self._real_repository.revision_graph_can_have_wrong_parents()
1733
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1735
return self._real_repository._find_inconsistent_revision_parents(
1738
def _check_for_inconsistent_revision_parents(self):
1740
return self._real_repository._check_for_inconsistent_revision_parents()
1742
def _make_parents_provider(self, other=None):
1743
providers = [self._unstacked_provider]
1744
if other is not None:
1745
providers.insert(0, other)
1746
providers.extend(r._make_parents_provider() for r in
1747
self._fallback_repositories)
1748
return graph.StackedParentsProvider(providers)
1750
def _serialise_search_recipe(self, recipe):
1751
"""Serialise a graph search recipe.
1753
:param recipe: A search recipe (start, stop, count).
1754
:return: Serialised bytes.
1756
start_keys = ' '.join(recipe[1])
1757
stop_keys = ' '.join(recipe[2])
1758
count = str(recipe[3])
1759
return '\n'.join((start_keys, stop_keys, count))
1761
def _serialise_search_result(self, search_result):
1762
if isinstance(search_result, graph.PendingAncestryResult):
1763
parts = ['ancestry-of']
1764
parts.extend(search_result.heads)
1766
recipe = search_result.get_recipe()
1767
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1768
return '\n'.join(parts)
1771
path = self.bzrdir._path_for_remote_call(self._client)
1773
response = self._call('PackRepository.autopack', path)
1774
except errors.UnknownSmartMethod:
1776
self._real_repository._pack_collection.autopack()
1779
if response[0] != 'ok':
1780
raise errors.UnexpectedSmartServerResponse(response)
1783
class RemoteStreamSink(repository.StreamSink):
1785
def _insert_real(self, stream, src_format, resume_tokens):
1786
self.target_repo._ensure_real()
1787
sink = self.target_repo._real_repository._get_sink()
1788
result = sink.insert_stream(stream, src_format, resume_tokens)
1790
self.target_repo.autopack()
1793
def insert_stream(self, stream, src_format, resume_tokens):
1794
target = self.target_repo
1795
target._unstacked_provider.missing_keys.clear()
1796
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1797
if target._lock_token:
1798
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1799
lock_args = (target._lock_token or '',)
1801
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1803
client = target._client
1804
medium = client._medium
1805
path = target.bzrdir._path_for_remote_call(client)
1806
# Probe for the verb to use with an empty stream before sending the
1807
# real stream to it. We do this both to avoid the risk of sending a
1808
# large request that is then rejected, and because we don't want to
1809
# implement a way to buffer, rewind, or restart the stream.
1811
for verb, required_version in candidate_calls:
1812
if medium._is_remote_before(required_version):
1815
# We've already done the probing (and set _is_remote_before) on
1816
# a previous insert.
1819
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1821
response = client.call_with_body_stream(
1822
(verb, path, '') + lock_args, byte_stream)
1823
except errors.UnknownSmartMethod:
1824
medium._remember_remote_is_before(required_version)
1830
return self._insert_real(stream, src_format, resume_tokens)
1831
self._last_inv_record = None
1832
self._last_substream = None
1833
if required_version < (1, 19):
1834
# Remote side doesn't support inventory deltas. Wrap the stream to
1835
# make sure we don't send any. If the stream contains inventory
1836
# deltas we'll interrupt the smart insert_stream request and
1838
stream = self._stop_stream_if_inventory_delta(stream)
1839
byte_stream = smart_repo._stream_to_byte_stream(
1841
resume_tokens = ' '.join(resume_tokens)
1842
response = client.call_with_body_stream(
1843
(verb, path, resume_tokens) + lock_args, byte_stream)
1844
if response[0][0] not in ('ok', 'missing-basis'):
1845
raise errors.UnexpectedSmartServerResponse(response)
1846
if self._last_substream is not None:
1847
# The stream included an inventory-delta record, but the remote
1848
# side isn't new enough to support them. So we need to send the
1849
# rest of the stream via VFS.
1850
self.target_repo.refresh_data()
1851
return self._resume_stream_with_vfs(response, src_format)
1852
if response[0][0] == 'missing-basis':
1853
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1854
resume_tokens = tokens
1855
return resume_tokens, set(missing_keys)
1857
self.target_repo.refresh_data()
1860
def _resume_stream_with_vfs(self, response, src_format):
1861
"""Resume sending a stream via VFS, first resending the record and
1862
substream that couldn't be sent via an insert_stream verb.
1864
if response[0][0] == 'missing-basis':
1865
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1866
# Ignore missing_keys, we haven't finished inserting yet
1869
def resume_substream():
1870
# Yield the substream that was interrupted.
1871
for record in self._last_substream:
1873
self._last_substream = None
1874
def resume_stream():
1875
# Finish sending the interrupted substream
1876
yield ('inventory-deltas', resume_substream())
1877
# Then simply continue sending the rest of the stream.
1878
for substream_kind, substream in self._last_stream:
1879
yield substream_kind, substream
1880
return self._insert_real(resume_stream(), src_format, tokens)
1882
def _stop_stream_if_inventory_delta(self, stream):
1883
"""Normally this just lets the original stream pass-through unchanged.
1885
However if any 'inventory-deltas' substream occurs it will stop
1886
streaming, and store the interrupted substream and stream in
1887
self._last_substream and self._last_stream so that the stream can be
1888
resumed by _resume_stream_with_vfs.
1891
stream_iter = iter(stream)
1892
for substream_kind, substream in stream_iter:
1893
if substream_kind == 'inventory-deltas':
1894
self._last_substream = substream
1895
self._last_stream = stream_iter
1898
yield substream_kind, substream
1901
class RemoteStreamSource(repository.StreamSource):
1902
"""Stream data from a remote server."""
1904
def get_stream(self, search):
1905
if (self.from_repository._fallback_repositories and
1906
self.to_format._fetch_order == 'topological'):
1907
return self._real_stream(self.from_repository, search)
1910
repos = [self.from_repository]
1916
repos.extend(repo._fallback_repositories)
1917
sources.append(repo)
1918
return self.missing_parents_chain(search, sources)
1920
def get_stream_for_missing_keys(self, missing_keys):
1921
self.from_repository._ensure_real()
1922
real_repo = self.from_repository._real_repository
1923
real_source = real_repo._get_source(self.to_format)
1924
return real_source.get_stream_for_missing_keys(missing_keys)
1926
def _real_stream(self, repo, search):
1927
"""Get a stream for search from repo.
1929
This never called RemoteStreamSource.get_stream, and is a heler
1930
for RemoteStreamSource._get_stream to allow getting a stream
1931
reliably whether fallback back because of old servers or trying
1932
to stream from a non-RemoteRepository (which the stacked support
1935
source = repo._get_source(self.to_format)
1936
if isinstance(source, RemoteStreamSource):
1938
source = repo._real_repository._get_source(self.to_format)
1939
return source.get_stream(search)
1941
def _get_stream(self, repo, search):
1942
"""Core worker to get a stream from repo for search.
1944
This is used by both get_stream and the stacking support logic. It
1945
deliberately gets a stream for repo which does not need to be
1946
self.from_repository. In the event that repo is not Remote, or
1947
cannot do a smart stream, a fallback is made to the generic
1948
repository._get_stream() interface, via self._real_stream.
1950
In the event of stacking, streams from _get_stream will not
1951
contain all the data for search - this is normal (see get_stream).
1953
:param repo: A repository.
1954
:param search: A search.
1956
# Fallbacks may be non-smart
1957
if not isinstance(repo, RemoteRepository):
1958
return self._real_stream(repo, search)
1959
client = repo._client
1960
medium = client._medium
1961
path = repo.bzrdir._path_for_remote_call(client)
1962
search_bytes = repo._serialise_search_result(search)
1963
args = (path, self.to_format.network_name())
1965
('Repository.get_stream_1.19', (1, 19)),
1966
('Repository.get_stream', (1, 13))]
1968
for verb, version in candidate_verbs:
1969
if medium._is_remote_before(version):
1972
response = repo._call_with_body_bytes_expecting_body(
1973
verb, args, search_bytes)
1974
except errors.UnknownSmartMethod:
1975
medium._remember_remote_is_before(version)
1977
response_tuple, response_handler = response
1981
return self._real_stream(repo, search)
1982
if response_tuple[0] != 'ok':
1983
raise errors.UnexpectedSmartServerResponse(response_tuple)
1984
byte_stream = response_handler.read_streamed_body()
1985
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
1986
self._record_counter)
1987
if src_format.network_name() != repo._format.network_name():
1988
raise AssertionError(
1989
"Mismatched RemoteRepository and stream src %r, %r" % (
1990
src_format.network_name(), repo._format.network_name()))
1993
def missing_parents_chain(self, search, sources):
1994
"""Chain multiple streams together to handle stacking.
1996
:param search: The overall search to satisfy with streams.
1997
:param sources: A list of Repository objects to query.
1999
self.from_serialiser = self.from_repository._format._serializer
2000
self.seen_revs = set()
2001
self.referenced_revs = set()
2002
# If there are heads in the search, or the key count is > 0, we are not
2004
while not search.is_empty() and len(sources) > 1:
2005
source = sources.pop(0)
2006
stream = self._get_stream(source, search)
2007
for kind, substream in stream:
2008
if kind != 'revisions':
2009
yield kind, substream
2011
yield kind, self.missing_parents_rev_handler(substream)
2012
search = search.refine(self.seen_revs, self.referenced_revs)
2013
self.seen_revs = set()
2014
self.referenced_revs = set()
2015
if not search.is_empty():
2016
for kind, stream in self._get_stream(sources[0], search):
2019
def missing_parents_rev_handler(self, substream):
2020
for content in substream:
2021
revision_bytes = content.get_bytes_as('fulltext')
2022
revision = self.from_serialiser.read_revision_from_string(
2024
self.seen_revs.add(content.key[-1])
2025
self.referenced_revs.update(revision.parent_ids)
2029
class RemoteBranchLockableFiles(LockableFiles):
2030
"""A 'LockableFiles' implementation that talks to a smart server.
2032
This is not a public interface class.
2035
def __init__(self, bzrdir, _client):
2036
self.bzrdir = bzrdir
2037
self._client = _client
2038
self._need_find_modes = True
2039
LockableFiles.__init__(
2040
self, bzrdir.get_branch_transport(None),
2041
'lock', lockdir.LockDir)
2043
def _find_modes(self):
2044
# RemoteBranches don't let the client set the mode of control files.
2045
self._dir_mode = None
2046
self._file_mode = None
2049
class RemoteBranchFormat(branch.BranchFormat):
2051
def __init__(self, network_name=None):
2052
super(RemoteBranchFormat, self).__init__()
2053
self._matchingbzrdir = RemoteBzrDirFormat()
2054
self._matchingbzrdir.set_branch_format(self)
2055
self._custom_format = None
2056
self._network_name = network_name
2058
def __eq__(self, other):
2059
return (isinstance(other, RemoteBranchFormat) and
2060
self.__dict__ == other.__dict__)
2062
def _ensure_real(self):
2063
if self._custom_format is None:
2064
self._custom_format = branch.network_format_registry.get(
2067
def get_format_description(self):
2069
return 'Remote: ' + self._custom_format.get_format_description()
2071
def network_name(self):
2072
return self._network_name
2074
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2075
return a_bzrdir.open_branch(name=name,
2076
ignore_fallbacks=ignore_fallbacks)
2078
def _vfs_initialize(self, a_bzrdir, name):
2079
# Initialisation when using a local bzrdir object, or a non-vfs init
2080
# method is not available on the server.
2081
# self._custom_format is always set - the start of initialize ensures
2083
if isinstance(a_bzrdir, RemoteBzrDir):
2084
a_bzrdir._ensure_real()
2085
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2088
# We assume the bzrdir is parameterised; it may not be.
2089
result = self._custom_format.initialize(a_bzrdir, name)
2090
if (isinstance(a_bzrdir, RemoteBzrDir) and
2091
not isinstance(result, RemoteBranch)):
2092
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2096
def initialize(self, a_bzrdir, name=None):
2097
# 1) get the network name to use.
2098
if self._custom_format:
2099
network_name = self._custom_format.network_name()
2101
# Select the current bzrlib default and ask for that.
2102
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2103
reference_format = reference_bzrdir_format.get_branch_format()
2104
self._custom_format = reference_format
2105
network_name = reference_format.network_name()
2106
# Being asked to create on a non RemoteBzrDir:
2107
if not isinstance(a_bzrdir, RemoteBzrDir):
2108
return self._vfs_initialize(a_bzrdir, name=name)
2109
medium = a_bzrdir._client._medium
2110
if medium._is_remote_before((1, 13)):
2111
return self._vfs_initialize(a_bzrdir, name=name)
2112
# Creating on a remote bzr dir.
2113
# 2) try direct creation via RPC
2114
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2115
if name is not None:
2116
# XXX JRV20100304: Support creating colocated branches
2117
raise errors.NoColocatedBranchSupport(self)
2118
verb = 'BzrDir.create_branch'
2120
response = a_bzrdir._call(verb, path, network_name)
2121
except errors.UnknownSmartMethod:
2122
# Fallback - use vfs methods
2123
medium._remember_remote_is_before((1, 13))
2124
return self._vfs_initialize(a_bzrdir, name=name)
2125
if response[0] != 'ok':
2126
raise errors.UnexpectedSmartServerResponse(response)
2127
# Turn the response into a RemoteRepository object.
2128
format = RemoteBranchFormat(network_name=response[1])
2129
repo_format = response_tuple_to_repo_format(response[3:])
2130
if response[2] == '':
2131
repo_bzrdir = a_bzrdir
2133
repo_bzrdir = RemoteBzrDir(
2134
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2136
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2137
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2138
format=format, setup_stacking=False, name=name)
2139
# XXX: We know this is a new branch, so it must have revno 0, revid
2140
# NULL_REVISION. Creating the branch locked would make this be unable
2141
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2142
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2143
return remote_branch
2145
def make_tags(self, branch):
2147
return self._custom_format.make_tags(branch)
2149
def supports_tags(self):
2150
# Remote branches might support tags, but we won't know until we
2151
# access the real remote branch.
2153
return self._custom_format.supports_tags()
2155
def supports_stacking(self):
2157
return self._custom_format.supports_stacking()
2159
def supports_set_append_revisions_only(self):
2161
return self._custom_format.supports_set_append_revisions_only()
2164
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2165
"""Branch stored on a server accessed by HPSS RPC.
2167
At the moment most operations are mapped down to simple file operations.
2170
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2171
_client=None, format=None, setup_stacking=True, name=None):
2172
"""Create a RemoteBranch instance.
2174
:param real_branch: An optional local implementation of the branch
2175
format, usually accessing the data via the VFS.
2176
:param _client: Private parameter for testing.
2177
:param format: A RemoteBranchFormat object, None to create one
2178
automatically. If supplied it should have a network_name already
2180
:param setup_stacking: If True make an RPC call to determine the
2181
stacked (or not) status of the branch. If False assume the branch
2183
:param name: Colocated branch name
2185
# We intentionally don't call the parent class's __init__, because it
2186
# will try to assign to self.tags, which is a property in this subclass.
2187
# And the parent's __init__ doesn't do much anyway.
2188
self.bzrdir = remote_bzrdir
2189
if _client is not None:
2190
self._client = _client
2192
self._client = remote_bzrdir._client
2193
self.repository = remote_repository
2194
if real_branch is not None:
2195
self._real_branch = real_branch
2196
# Give the remote repository the matching real repo.
2197
real_repo = self._real_branch.repository
2198
if isinstance(real_repo, RemoteRepository):
2199
real_repo._ensure_real()
2200
real_repo = real_repo._real_repository
2201
self.repository._set_real_repository(real_repo)
2202
# Give the branch the remote repository to let fast-pathing happen.
2203
self._real_branch.repository = self.repository
2205
self._real_branch = None
2206
# Fill out expected attributes of branch for bzrlib API users.
2207
self._clear_cached_state()
2208
# TODO: deprecate self.base in favor of user_url
2209
self.base = self.bzrdir.user_url
2211
self._control_files = None
2212
self._lock_mode = None
2213
self._lock_token = None
2214
self._repo_lock_token = None
2215
self._lock_count = 0
2216
self._leave_lock = False
2217
# Setup a format: note that we cannot call _ensure_real until all the
2218
# attributes above are set: This code cannot be moved higher up in this
2221
self._format = RemoteBranchFormat()
2222
if real_branch is not None:
2223
self._format._network_name = \
2224
self._real_branch._format.network_name()
2226
self._format = format
2227
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2228
# branch.open_branch method.
2229
self._real_ignore_fallbacks = not setup_stacking
2230
if not self._format._network_name:
2231
# Did not get from open_branchV2 - old server.
2233
self._format._network_name = \
2234
self._real_branch._format.network_name()
2235
self.tags = self._format.make_tags(self)
2236
# The base class init is not called, so we duplicate this:
2237
hooks = branch.Branch.hooks['open']
2240
self._is_stacked = False
2242
self._setup_stacking()
2244
def _setup_stacking(self):
2245
# configure stacking into the remote repository, by reading it from
2248
fallback_url = self.get_stacked_on_url()
2249
except (errors.NotStacked, errors.UnstackableBranchFormat,
2250
errors.UnstackableRepositoryFormat), e:
2252
self._is_stacked = True
2253
self._activate_fallback_location(fallback_url)
2255
def _get_config(self):
2256
return RemoteBranchConfig(self)
2258
def _get_real_transport(self):
2259
# if we try vfs access, return the real branch's vfs transport
2261
return self._real_branch._transport
2263
_transport = property(_get_real_transport)
2266
return "%s(%s)" % (self.__class__.__name__, self.base)
2270
def _ensure_real(self):
2271
"""Ensure that there is a _real_branch set.
2273
Used before calls to self._real_branch.
2275
if self._real_branch is None:
2276
if not vfs.vfs_enabled():
2277
raise AssertionError('smart server vfs must be enabled '
2278
'to use vfs implementation')
2279
self.bzrdir._ensure_real()
2280
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2281
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2282
if self.repository._real_repository is None:
2283
# Give the remote repository the matching real repo.
2284
real_repo = self._real_branch.repository
2285
if isinstance(real_repo, RemoteRepository):
2286
real_repo._ensure_real()
2287
real_repo = real_repo._real_repository
2288
self.repository._set_real_repository(real_repo)
2289
# Give the real branch the remote repository to let fast-pathing
2291
self._real_branch.repository = self.repository
2292
if self._lock_mode == 'r':
2293
self._real_branch.lock_read()
2294
elif self._lock_mode == 'w':
2295
self._real_branch.lock_write(token=self._lock_token)
2297
def _translate_error(self, err, **context):
2298
self.repository._translate_error(err, branch=self, **context)
2300
def _clear_cached_state(self):
2301
super(RemoteBranch, self)._clear_cached_state()
2302
if self._real_branch is not None:
2303
self._real_branch._clear_cached_state()
2305
def _clear_cached_state_of_remote_branch_only(self):
2306
"""Like _clear_cached_state, but doesn't clear the cache of
2309
This is useful when falling back to calling a method of
2310
self._real_branch that changes state. In that case the underlying
2311
branch changes, so we need to invalidate this RemoteBranch's cache of
2312
it. However, there's no need to invalidate the _real_branch's cache
2313
too, in fact doing so might harm performance.
2315
super(RemoteBranch, self)._clear_cached_state()
2318
def control_files(self):
2319
# Defer actually creating RemoteBranchLockableFiles until its needed,
2320
# because it triggers an _ensure_real that we otherwise might not need.
2321
if self._control_files is None:
2322
self._control_files = RemoteBranchLockableFiles(
2323
self.bzrdir, self._client)
2324
return self._control_files
2326
def _get_checkout_format(self):
2328
return self._real_branch._get_checkout_format()
2330
def get_physical_lock_status(self):
2331
"""See Branch.get_physical_lock_status()."""
2332
# should be an API call to the server, as branches must be lockable.
2334
return self._real_branch.get_physical_lock_status()
2336
def get_stacked_on_url(self):
2337
"""Get the URL this branch is stacked against.
2339
:raises NotStacked: If the branch is not stacked.
2340
:raises UnstackableBranchFormat: If the branch does not support
2342
:raises UnstackableRepositoryFormat: If the repository does not support
2346
# there may not be a repository yet, so we can't use
2347
# self._translate_error, so we can't use self._call either.
2348
response = self._client.call('Branch.get_stacked_on_url',
2349
self._remote_path())
2350
except errors.ErrorFromSmartServer, err:
2351
# there may not be a repository yet, so we can't call through
2352
# its _translate_error
2353
_translate_error(err, branch=self)
2354
except errors.UnknownSmartMethod, err:
2356
return self._real_branch.get_stacked_on_url()
2357
if response[0] != 'ok':
2358
raise errors.UnexpectedSmartServerResponse(response)
2361
def set_stacked_on_url(self, url):
2362
branch.Branch.set_stacked_on_url(self, url)
2364
self._is_stacked = False
2366
self._is_stacked = True
2368
def _vfs_get_tags_bytes(self):
2370
return self._real_branch._get_tags_bytes()
2372
def _get_tags_bytes(self):
2373
medium = self._client._medium
2374
if medium._is_remote_before((1, 13)):
2375
return self._vfs_get_tags_bytes()
2377
response = self._call('Branch.get_tags_bytes', self._remote_path())
2378
except errors.UnknownSmartMethod:
2379
medium._remember_remote_is_before((1, 13))
2380
return self._vfs_get_tags_bytes()
2383
def _vfs_set_tags_bytes(self, bytes):
2385
return self._real_branch._set_tags_bytes(bytes)
2387
def _set_tags_bytes(self, bytes):
2388
medium = self._client._medium
2389
if medium._is_remote_before((1, 18)):
2390
self._vfs_set_tags_bytes(bytes)
2394
self._remote_path(), self._lock_token, self._repo_lock_token)
2395
response = self._call_with_body_bytes(
2396
'Branch.set_tags_bytes', args, bytes)
2397
except errors.UnknownSmartMethod:
2398
medium._remember_remote_is_before((1, 18))
2399
self._vfs_set_tags_bytes(bytes)
2401
def lock_read(self):
2402
"""Lock the branch for read operations.
2404
:return: A bzrlib.lock.LogicalLockResult.
2406
self.repository.lock_read()
2407
if not self._lock_mode:
2408
self._note_lock('r')
2409
self._lock_mode = 'r'
2410
self._lock_count = 1
2411
if self._real_branch is not None:
2412
self._real_branch.lock_read()
2414
self._lock_count += 1
2415
return lock.LogicalLockResult(self.unlock)
2417
def _remote_lock_write(self, token):
2419
branch_token = repo_token = ''
2421
branch_token = token
2422
repo_token = self.repository.lock_write().repository_token
2423
self.repository.unlock()
2424
err_context = {'token': token}
2426
response = self._call(
2427
'Branch.lock_write', self._remote_path(), branch_token,
2428
repo_token or '', **err_context)
2429
except errors.LockContention, e:
2430
# The LockContention from the server doesn't have any
2431
# information about the lock_url. We re-raise LockContention
2432
# with valid lock_url.
2433
raise errors.LockContention('(remote lock)',
2434
self.repository.base.split('.bzr/')[0])
2435
if response[0] != 'ok':
2436
raise errors.UnexpectedSmartServerResponse(response)
2437
ok, branch_token, repo_token = response
2438
return branch_token, repo_token
2440
def lock_write(self, token=None):
2441
if not self._lock_mode:
2442
self._note_lock('w')
2443
# Lock the branch and repo in one remote call.
2444
remote_tokens = self._remote_lock_write(token)
2445
self._lock_token, self._repo_lock_token = remote_tokens
2446
if not self._lock_token:
2447
raise SmartProtocolError('Remote server did not return a token!')
2448
# Tell the self.repository object that it is locked.
2449
self.repository.lock_write(
2450
self._repo_lock_token, _skip_rpc=True)
2452
if self._real_branch is not None:
2453
self._real_branch.lock_write(token=self._lock_token)
2454
if token is not None:
2455
self._leave_lock = True
2457
self._leave_lock = False
2458
self._lock_mode = 'w'
2459
self._lock_count = 1
2460
elif self._lock_mode == 'r':
2461
raise errors.ReadOnlyError(self)
2463
if token is not None:
2464
# A token was given to lock_write, and we're relocking, so
2465
# check that the given token actually matches the one we
2467
if token != self._lock_token:
2468
raise errors.TokenMismatch(token, self._lock_token)
2469
self._lock_count += 1
2470
# Re-lock the repository too.
2471
self.repository.lock_write(self._repo_lock_token)
2472
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2474
def _unlock(self, branch_token, repo_token):
2475
err_context = {'token': str((branch_token, repo_token))}
2476
response = self._call(
2477
'Branch.unlock', self._remote_path(), branch_token,
2478
repo_token or '', **err_context)
2479
if response == ('ok',):
2481
raise errors.UnexpectedSmartServerResponse(response)
2483
@only_raises(errors.LockNotHeld, errors.LockBroken)
2486
self._lock_count -= 1
2487
if not self._lock_count:
2488
self._clear_cached_state()
2489
mode = self._lock_mode
2490
self._lock_mode = None
2491
if self._real_branch is not None:
2492
if (not self._leave_lock and mode == 'w' and
2493
self._repo_lock_token):
2494
# If this RemoteBranch will remove the physical lock
2495
# for the repository, make sure the _real_branch
2496
# doesn't do it first. (Because the _real_branch's
2497
# repository is set to be the RemoteRepository.)
2498
self._real_branch.repository.leave_lock_in_place()
2499
self._real_branch.unlock()
2501
# Only write-locked branched need to make a remote method
2502
# call to perform the unlock.
2504
if not self._lock_token:
2505
raise AssertionError('Locked, but no token!')
2506
branch_token = self._lock_token
2507
repo_token = self._repo_lock_token
2508
self._lock_token = None
2509
self._repo_lock_token = None
2510
if not self._leave_lock:
2511
self._unlock(branch_token, repo_token)
2513
self.repository.unlock()
2515
def break_lock(self):
2517
return self._real_branch.break_lock()
2519
def leave_lock_in_place(self):
2520
if not self._lock_token:
2521
raise NotImplementedError(self.leave_lock_in_place)
2522
self._leave_lock = True
2524
def dont_leave_lock_in_place(self):
2525
if not self._lock_token:
2526
raise NotImplementedError(self.dont_leave_lock_in_place)
2527
self._leave_lock = False
2530
def get_rev_id(self, revno, history=None):
2532
return _mod_revision.NULL_REVISION
2533
last_revision_info = self.last_revision_info()
2534
ok, result = self.repository.get_rev_id_for_revno(
2535
revno, last_revision_info)
2538
missing_parent = result[1]
2539
# Either the revision named by the server is missing, or its parent
2540
# is. Call get_parent_map to determine which, so that we report a
2542
parent_map = self.repository.get_parent_map([missing_parent])
2543
if missing_parent in parent_map:
2544
missing_parent = parent_map[missing_parent]
2545
raise errors.RevisionNotPresent(missing_parent, self.repository)
2547
def _last_revision_info(self):
2548
response = self._call('Branch.last_revision_info', self._remote_path())
2549
if response[0] != 'ok':
2550
raise SmartProtocolError('unexpected response code %s' % (response,))
2551
revno = int(response[1])
2552
last_revision = response[2]
2553
return (revno, last_revision)
2555
def _gen_revision_history(self):
2556
"""See Branch._gen_revision_history()."""
2557
if self._is_stacked:
2559
return self._real_branch._gen_revision_history()
2560
response_tuple, response_handler = self._call_expecting_body(
2561
'Branch.revision_history', self._remote_path())
2562
if response_tuple[0] != 'ok':
2563
raise errors.UnexpectedSmartServerResponse(response_tuple)
2564
result = response_handler.read_body_bytes().split('\x00')
2569
def _remote_path(self):
2570
return self.bzrdir._path_for_remote_call(self._client)
2572
def _set_last_revision_descendant(self, revision_id, other_branch,
2573
allow_diverged=False, allow_overwrite_descendant=False):
2574
# This performs additional work to meet the hook contract; while its
2575
# undesirable, we have to synthesise the revno to call the hook, and
2576
# not calling the hook is worse as it means changes can't be prevented.
2577
# Having calculated this though, we can't just call into
2578
# set_last_revision_info as a simple call, because there is a set_rh
2579
# hook that some folk may still be using.
2580
old_revno, old_revid = self.last_revision_info()
2581
history = self._lefthand_history(revision_id)
2582
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2583
err_context = {'other_branch': other_branch}
2584
response = self._call('Branch.set_last_revision_ex',
2585
self._remote_path(), self._lock_token, self._repo_lock_token,
2586
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2588
self._clear_cached_state()
2589
if len(response) != 3 and response[0] != 'ok':
2590
raise errors.UnexpectedSmartServerResponse(response)
2591
new_revno, new_revision_id = response[1:]
2592
self._last_revision_info_cache = new_revno, new_revision_id
2593
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2594
if self._real_branch is not None:
2595
cache = new_revno, new_revision_id
2596
self._real_branch._last_revision_info_cache = cache
2598
def _set_last_revision(self, revision_id):
2599
old_revno, old_revid = self.last_revision_info()
2600
# This performs additional work to meet the hook contract; while its
2601
# undesirable, we have to synthesise the revno to call the hook, and
2602
# not calling the hook is worse as it means changes can't be prevented.
2603
# Having calculated this though, we can't just call into
2604
# set_last_revision_info as a simple call, because there is a set_rh
2605
# hook that some folk may still be using.
2606
history = self._lefthand_history(revision_id)
2607
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2608
self._clear_cached_state()
2609
response = self._call('Branch.set_last_revision',
2610
self._remote_path(), self._lock_token, self._repo_lock_token,
2612
if response != ('ok',):
2613
raise errors.UnexpectedSmartServerResponse(response)
2614
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2617
def set_revision_history(self, rev_history):
2618
# Send just the tip revision of the history; the server will generate
2619
# the full history from that. If the revision doesn't exist in this
2620
# branch, NoSuchRevision will be raised.
2621
if rev_history == []:
2624
rev_id = rev_history[-1]
2625
self._set_last_revision(rev_id)
2626
for hook in branch.Branch.hooks['set_rh']:
2627
hook(self, rev_history)
2628
self._cache_revision_history(rev_history)
2630
def _get_parent_location(self):
2631
medium = self._client._medium
2632
if medium._is_remote_before((1, 13)):
2633
return self._vfs_get_parent_location()
2635
response = self._call('Branch.get_parent', self._remote_path())
2636
except errors.UnknownSmartMethod:
2637
medium._remember_remote_is_before((1, 13))
2638
return self._vfs_get_parent_location()
2639
if len(response) != 1:
2640
raise errors.UnexpectedSmartServerResponse(response)
2641
parent_location = response[0]
2642
if parent_location == '':
2644
return parent_location
2646
def _vfs_get_parent_location(self):
2648
return self._real_branch._get_parent_location()
2650
def _set_parent_location(self, url):
2651
medium = self._client._medium
2652
if medium._is_remote_before((1, 15)):
2653
return self._vfs_set_parent_location(url)
2655
call_url = url or ''
2656
if type(call_url) is not str:
2657
raise AssertionError('url must be a str or None (%s)' % url)
2658
response = self._call('Branch.set_parent_location',
2659
self._remote_path(), self._lock_token, self._repo_lock_token,
2661
except errors.UnknownSmartMethod:
2662
medium._remember_remote_is_before((1, 15))
2663
return self._vfs_set_parent_location(url)
2665
raise errors.UnexpectedSmartServerResponse(response)
2667
def _vfs_set_parent_location(self, url):
2669
return self._real_branch._set_parent_location(url)
2672
def pull(self, source, overwrite=False, stop_revision=None,
2674
self._clear_cached_state_of_remote_branch_only()
2676
return self._real_branch.pull(
2677
source, overwrite=overwrite, stop_revision=stop_revision,
2678
_override_hook_target=self, **kwargs)
2681
def push(self, target, overwrite=False, stop_revision=None):
2683
return self._real_branch.push(
2684
target, overwrite=overwrite, stop_revision=stop_revision,
2685
_override_hook_source_branch=self)
2687
def is_locked(self):
2688
return self._lock_count >= 1
2691
def revision_id_to_revno(self, revision_id):
2693
return self._real_branch.revision_id_to_revno(revision_id)
2696
def set_last_revision_info(self, revno, revision_id):
2697
# XXX: These should be returned by the set_last_revision_info verb
2698
old_revno, old_revid = self.last_revision_info()
2699
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2700
revision_id = ensure_null(revision_id)
2702
response = self._call('Branch.set_last_revision_info',
2703
self._remote_path(), self._lock_token, self._repo_lock_token,
2704
str(revno), revision_id)
2705
except errors.UnknownSmartMethod:
2707
self._clear_cached_state_of_remote_branch_only()
2708
self._real_branch.set_last_revision_info(revno, revision_id)
2709
self._last_revision_info_cache = revno, revision_id
2711
if response == ('ok',):
2712
self._clear_cached_state()
2713
self._last_revision_info_cache = revno, revision_id
2714
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2715
# Update the _real_branch's cache too.
2716
if self._real_branch is not None:
2717
cache = self._last_revision_info_cache
2718
self._real_branch._last_revision_info_cache = cache
2720
raise errors.UnexpectedSmartServerResponse(response)
2723
def generate_revision_history(self, revision_id, last_rev=None,
2725
medium = self._client._medium
2726
if not medium._is_remote_before((1, 6)):
2727
# Use a smart method for 1.6 and above servers
2729
self._set_last_revision_descendant(revision_id, other_branch,
2730
allow_diverged=True, allow_overwrite_descendant=True)
2732
except errors.UnknownSmartMethod:
2733
medium._remember_remote_is_before((1, 6))
2734
self._clear_cached_state_of_remote_branch_only()
2735
self.set_revision_history(self._lefthand_history(revision_id,
2736
last_rev=last_rev,other_branch=other_branch))
2738
def set_push_location(self, location):
2740
return self._real_branch.set_push_location(location)
2743
class RemoteConfig(object):
2744
"""A Config that reads and writes from smart verbs.
2746
It is a low-level object that considers config data to be name/value pairs
2747
that may be associated with a section. Assigning meaning to the these
2748
values is done at higher levels like bzrlib.config.TreeConfig.
2751
def get_option(self, name, section=None, default=None):
2752
"""Return the value associated with a named option.
2754
:param name: The name of the value
2755
:param section: The section the option is in (if any)
2756
:param default: The value to return if the value is not set
2757
:return: The value or default value
2760
configobj = self._get_configobj()
2762
section_obj = configobj
2765
section_obj = configobj[section]
2768
return section_obj.get(name, default)
2769
except errors.UnknownSmartMethod:
2770
return self._vfs_get_option(name, section, default)
2772
def _response_to_configobj(self, response):
2773
if len(response[0]) and response[0][0] != 'ok':
2774
raise errors.UnexpectedSmartServerResponse(response)
2775
lines = response[1].read_body_bytes().splitlines()
2776
return config.ConfigObj(lines, encoding='utf-8')
2779
class RemoteBranchConfig(RemoteConfig):
2780
"""A RemoteConfig for Branches."""
2782
def __init__(self, branch):
2783
self._branch = branch
2785
def _get_configobj(self):
2786
path = self._branch._remote_path()
2787
response = self._branch._client.call_expecting_body(
2788
'Branch.get_config_file', path)
2789
return self._response_to_configobj(response)
2791
def set_option(self, value, name, section=None):
2792
"""Set the value associated with a named option.
2794
:param value: The value to set
2795
:param name: The name of the value to set
2796
:param section: The section the option is in (if any)
2798
medium = self._branch._client._medium
2799
if medium._is_remote_before((1, 14)):
2800
return self._vfs_set_option(value, name, section)
2801
if isinstance(value, dict):
2802
if medium._is_remote_before((2, 2)):
2803
return self._vfs_set_option(value, name, section)
2804
return self._set_config_option_dict(value, name, section)
2806
return self._set_config_option(value, name, section)
2808
def _set_config_option(self, value, name, section):
2810
path = self._branch._remote_path()
2811
response = self._branch._client.call('Branch.set_config_option',
2812
path, self._branch._lock_token, self._branch._repo_lock_token,
2813
value.encode('utf8'), name, section or '')
2814
except errors.UnknownSmartMethod:
2815
medium = self._branch._client._medium
2816
medium._remember_remote_is_before((1, 14))
2817
return self._vfs_set_option(value, name, section)
2819
raise errors.UnexpectedSmartServerResponse(response)
2821
def _serialize_option_dict(self, option_dict):
2823
for key, value in option_dict.items():
2824
if isinstance(key, unicode):
2825
key = key.encode('utf8')
2826
if isinstance(value, unicode):
2827
value = value.encode('utf8')
2828
utf8_dict[key] = value
2829
return bencode.bencode(utf8_dict)
2831
def _set_config_option_dict(self, value, name, section):
2833
path = self._branch._remote_path()
2834
serialised_dict = self._serialize_option_dict(value)
2835
response = self._branch._client.call(
2836
'Branch.set_config_option_dict',
2837
path, self._branch._lock_token, self._branch._repo_lock_token,
2838
serialised_dict, name, section or '')
2839
except errors.UnknownSmartMethod:
2840
medium = self._branch._client._medium
2841
medium._remember_remote_is_before((2, 2))
2842
return self._vfs_set_option(value, name, section)
2844
raise errors.UnexpectedSmartServerResponse(response)
2846
def _real_object(self):
2847
self._branch._ensure_real()
2848
return self._branch._real_branch
2850
def _vfs_set_option(self, value, name, section=None):
2851
return self._real_object()._get_config().set_option(
2852
value, name, section)
2855
class RemoteBzrDirConfig(RemoteConfig):
2856
"""A RemoteConfig for BzrDirs."""
2858
def __init__(self, bzrdir):
2859
self._bzrdir = bzrdir
2861
def _get_configobj(self):
2862
medium = self._bzrdir._client._medium
2863
verb = 'BzrDir.get_config_file'
2864
if medium._is_remote_before((1, 15)):
2865
raise errors.UnknownSmartMethod(verb)
2866
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2867
response = self._bzrdir._call_expecting_body(
2869
return self._response_to_configobj(response)
2871
def _vfs_get_option(self, name, section, default):
2872
return self._real_object()._get_config().get_option(
2873
name, section, default)
2875
def set_option(self, value, name, section=None):
2876
"""Set the value associated with a named option.
2878
:param value: The value to set
2879
:param name: The name of the value to set
2880
:param section: The section the option is in (if any)
2882
return self._real_object()._get_config().set_option(
2883
value, name, section)
2885
def _real_object(self):
2886
self._bzrdir._ensure_real()
2887
return self._bzrdir._real_bzrdir
2891
def _extract_tar(tar, to_dir):
2892
"""Extract all the contents of a tarfile object.
2894
A replacement for extractall, which is not present in python2.4
2897
tar.extract(tarinfo, to_dir)
2900
def _translate_error(err, **context):
2901
"""Translate an ErrorFromSmartServer into a more useful error.
2903
Possible context keys:
2911
If the error from the server doesn't match a known pattern, then
2912
UnknownErrorFromSmartServer is raised.
2916
return context[name]
2917
except KeyError, key_err:
2918
mutter('Missing key %r in context %r', key_err.args[0], context)
2921
"""Get the path from the context if present, otherwise use first error
2925
return context['path']
2926
except KeyError, key_err:
2928
return err.error_args[0]
2929
except IndexError, idx_err:
2931
'Missing key %r in context %r', key_err.args[0], context)
2934
if err.error_verb == 'IncompatibleRepositories':
2935
raise errors.IncompatibleRepositories(err.error_args[0],
2936
err.error_args[1], err.error_args[2])
2937
elif err.error_verb == 'NoSuchRevision':
2938
raise NoSuchRevision(find('branch'), err.error_args[0])
2939
elif err.error_verb == 'nosuchrevision':
2940
raise NoSuchRevision(find('repository'), err.error_args[0])
2941
elif err.error_verb == 'nobranch':
2942
if len(err.error_args) >= 1:
2943
extra = err.error_args[0]
2946
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2948
elif err.error_verb == 'norepository':
2949
raise errors.NoRepositoryPresent(find('bzrdir'))
2950
elif err.error_verb == 'LockContention':
2951
raise errors.LockContention('(remote lock)')
2952
elif err.error_verb == 'UnlockableTransport':
2953
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2954
elif err.error_verb == 'LockFailed':
2955
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2956
elif err.error_verb == 'TokenMismatch':
2957
raise errors.TokenMismatch(find('token'), '(remote token)')
2958
elif err.error_verb == 'Diverged':
2959
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2960
elif err.error_verb == 'TipChangeRejected':
2961
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2962
elif err.error_verb == 'UnstackableBranchFormat':
2963
raise errors.UnstackableBranchFormat(*err.error_args)
2964
elif err.error_verb == 'UnstackableRepositoryFormat':
2965
raise errors.UnstackableRepositoryFormat(*err.error_args)
2966
elif err.error_verb == 'NotStacked':
2967
raise errors.NotStacked(branch=find('branch'))
2968
elif err.error_verb == 'PermissionDenied':
2970
if len(err.error_args) >= 2:
2971
extra = err.error_args[1]
2974
raise errors.PermissionDenied(path, extra=extra)
2975
elif err.error_verb == 'ReadError':
2977
raise errors.ReadError(path)
2978
elif err.error_verb == 'NoSuchFile':
2980
raise errors.NoSuchFile(path)
2981
elif err.error_verb == 'FileExists':
2982
raise errors.FileExists(err.error_args[0])
2983
elif err.error_verb == 'DirectoryNotEmpty':
2984
raise errors.DirectoryNotEmpty(err.error_args[0])
2985
elif err.error_verb == 'ShortReadvError':
2986
args = err.error_args
2987
raise errors.ShortReadvError(
2988
args[0], int(args[1]), int(args[2]), int(args[3]))
2989
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2990
encoding = str(err.error_args[0]) # encoding must always be a string
2991
val = err.error_args[1]
2992
start = int(err.error_args[2])
2993
end = int(err.error_args[3])
2994
reason = str(err.error_args[4]) # reason must always be a string
2995
if val.startswith('u:'):
2996
val = val[2:].decode('utf-8')
2997
elif val.startswith('s:'):
2998
val = val[2:].decode('base64')
2999
if err.error_verb == 'UnicodeDecodeError':
3000
raise UnicodeDecodeError(encoding, val, start, end, reason)
3001
elif err.error_verb == 'UnicodeEncodeError':
3002
raise UnicodeEncodeError(encoding, val, start, end, reason)
3003
elif err.error_verb == 'ReadOnlyError':
3004
raise errors.TransportNotPossible('readonly transport')
3005
raise errors.UnknownErrorFromSmartServer(err)