1
# Copyright (C) 2006-2011 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,
38
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
39
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
40
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
41
from bzrlib.errors import (
45
from bzrlib.lockable_files import LockableFiles
46
from bzrlib.smart import client, vfs, repository as smart_repo
47
from bzrlib.revision import ensure_null, NULL_REVISION
48
from bzrlib.repository import RepositoryWriteLockResult
49
from bzrlib.trace import mutter, note, warning
52
class _RpcHelper(object):
53
"""Mixin class that helps with issuing RPCs."""
55
def _call(self, method, *args, **err_context):
57
return self._client.call(method, *args)
58
except errors.ErrorFromSmartServer, err:
59
self._translate_error(err, **err_context)
61
def _call_expecting_body(self, method, *args, **err_context):
63
return self._client.call_expecting_body(method, *args)
64
except errors.ErrorFromSmartServer, err:
65
self._translate_error(err, **err_context)
67
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
69
return self._client.call_with_body_bytes(method, args, body_bytes)
70
except errors.ErrorFromSmartServer, err:
71
self._translate_error(err, **err_context)
73
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
76
return self._client.call_with_body_bytes_expecting_body(
77
method, args, body_bytes)
78
except errors.ErrorFromSmartServer, err:
79
self._translate_error(err, **err_context)
82
def response_tuple_to_repo_format(response):
83
"""Convert a response tuple describing a repository format to a format."""
84
format = RemoteRepositoryFormat()
85
format._rich_root_data = (response[0] == 'yes')
86
format._supports_tree_reference = (response[1] == 'yes')
87
format._supports_external_lookups = (response[2] == 'yes')
88
format._network_name = response[3]
92
# Note: RemoteBzrDirFormat is in bzrdir.py
94
class RemoteBzrDir(BzrDir, _RpcHelper):
95
"""Control directory on a remote server, accessed via bzr:// or similar."""
97
def __init__(self, transport, format, _client=None, _force_probe=False):
98
"""Construct a RemoteBzrDir.
100
:param _client: Private parameter for testing. Disables probing and the
101
use of a real bzrdir.
103
BzrDir.__init__(self, transport, format)
104
# this object holds a delegated bzrdir that uses file-level operations
105
# to talk to the other side
106
self._real_bzrdir = None
107
self._has_working_tree = None
108
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
109
# create_branch for details.
110
self._next_open_branch_result = None
113
medium = transport.get_smart_medium()
114
self._client = client._SmartClient(medium)
116
self._client = _client
123
return '%s(%r)' % (self.__class__.__name__, self._client)
125
def _probe_bzrdir(self):
126
medium = self._client._medium
127
path = self._path_for_remote_call(self._client)
128
if medium._is_remote_before((2, 1)):
132
self._rpc_open_2_1(path)
134
except errors.UnknownSmartMethod:
135
medium._remember_remote_is_before((2, 1))
138
def _rpc_open_2_1(self, path):
139
response = self._call('BzrDir.open_2.1', path)
140
if response == ('no',):
141
raise errors.NotBranchError(path=self.root_transport.base)
142
elif response[0] == 'yes':
143
if response[1] == 'yes':
144
self._has_working_tree = True
145
elif response[1] == 'no':
146
self._has_working_tree = False
148
raise errors.UnexpectedSmartServerResponse(response)
150
raise errors.UnexpectedSmartServerResponse(response)
152
def _rpc_open(self, path):
153
response = self._call('BzrDir.open', path)
154
if response not in [('yes',), ('no',)]:
155
raise errors.UnexpectedSmartServerResponse(response)
156
if response == ('no',):
157
raise errors.NotBranchError(path=self.root_transport.base)
159
def _ensure_real(self):
160
"""Ensure that there is a _real_bzrdir set.
162
Used before calls to self._real_bzrdir.
164
if not self._real_bzrdir:
165
if 'hpssvfs' in debug.debug_flags:
167
warning('VFS BzrDir access triggered\n%s',
168
''.join(traceback.format_stack()))
169
self._real_bzrdir = BzrDir.open_from_transport(
170
self.root_transport, _server_formats=False)
171
self._format._network_name = \
172
self._real_bzrdir._format.network_name()
174
def _translate_error(self, err, **context):
175
_translate_error(err, bzrdir=self, **context)
177
def break_lock(self):
178
# Prevent aliasing problems in the next_open_branch_result cache.
179
# See create_branch for rationale.
180
self._next_open_branch_result = None
181
return BzrDir.break_lock(self)
183
def _vfs_cloning_metadir(self, require_stacking=False):
185
return self._real_bzrdir.cloning_metadir(
186
require_stacking=require_stacking)
188
def cloning_metadir(self, require_stacking=False):
189
medium = self._client._medium
190
if medium._is_remote_before((1, 13)):
191
return self._vfs_cloning_metadir(require_stacking=require_stacking)
192
verb = 'BzrDir.cloning_metadir'
197
path = self._path_for_remote_call(self._client)
199
response = self._call(verb, path, stacking)
200
except errors.UnknownSmartMethod:
201
medium._remember_remote_is_before((1, 13))
202
return self._vfs_cloning_metadir(require_stacking=require_stacking)
203
except errors.UnknownErrorFromSmartServer, err:
204
if err.error_tuple != ('BranchReference',):
206
# We need to resolve the branch reference to determine the
207
# cloning_metadir. This causes unnecessary RPCs to open the
208
# referenced branch (and bzrdir, etc) but only when the caller
209
# didn't already resolve the branch reference.
210
referenced_branch = self.open_branch()
211
return referenced_branch.bzrdir.cloning_metadir()
212
if len(response) != 3:
213
raise errors.UnexpectedSmartServerResponse(response)
214
control_name, repo_name, branch_info = response
215
if len(branch_info) != 2:
216
raise errors.UnexpectedSmartServerResponse(response)
217
branch_ref, branch_name = branch_info
218
format = controldir.network_format_registry.get(control_name)
220
format.repository_format = repository.network_format_registry.get(
222
if branch_ref == 'ref':
223
# XXX: we need possible_transports here to avoid reopening the
224
# connection to the referenced location
225
ref_bzrdir = BzrDir.open(branch_name)
226
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
227
format.set_branch_format(branch_format)
228
elif branch_ref == 'branch':
230
format.set_branch_format(
231
branch.network_format_registry.get(branch_name))
233
raise errors.UnexpectedSmartServerResponse(response)
236
def create_repository(self, shared=False):
237
# as per meta1 formats - just delegate to the format object which may
239
result = self._format.repository_format.initialize(self, shared)
240
if not isinstance(result, RemoteRepository):
241
return self.open_repository()
245
def destroy_repository(self):
246
"""See BzrDir.destroy_repository"""
248
self._real_bzrdir.destroy_repository()
250
def create_branch(self, name=None, repository=None):
251
# as per meta1 formats - just delegate to the format object which may
253
real_branch = self._format.get_branch_format().initialize(self,
254
name=name, repository=repository)
255
if not isinstance(real_branch, RemoteBranch):
256
if not isinstance(repository, RemoteRepository):
257
raise AssertionError(
258
'need a RemoteRepository to use with RemoteBranch, got %r'
260
result = RemoteBranch(self, repository, real_branch, name=name)
263
# BzrDir.clone_on_transport() uses the result of create_branch but does
264
# not return it to its callers; we save approximately 8% of our round
265
# trips by handing the branch we created back to the first caller to
266
# open_branch rather than probing anew. Long term we need a API in
267
# bzrdir that doesn't discard result objects (like result_branch).
269
self._next_open_branch_result = result
272
def destroy_branch(self, name=None):
273
"""See BzrDir.destroy_branch"""
275
self._real_bzrdir.destroy_branch(name=name)
276
self._next_open_branch_result = None
278
def create_workingtree(self, revision_id=None, from_branch=None,
279
accelerator_tree=None, hardlink=False):
280
raise errors.NotLocalUrl(self.transport.base)
282
def find_branch_format(self, name=None):
283
"""Find the branch 'format' for this bzrdir.
285
This might be a synthetic object for e.g. RemoteBranch and SVN.
287
b = self.open_branch(name=name)
290
def get_branch_reference(self, name=None):
291
"""See BzrDir.get_branch_reference()."""
293
# XXX JRV20100304: Support opening colocated branches
294
raise errors.NoColocatedBranchSupport(self)
295
response = self._get_branch_reference()
296
if response[0] == 'ref':
301
def _get_branch_reference(self):
302
path = self._path_for_remote_call(self._client)
303
medium = self._client._medium
305
('BzrDir.open_branchV3', (2, 1)),
306
('BzrDir.open_branchV2', (1, 13)),
307
('BzrDir.open_branch', None),
309
for verb, required_version in candidate_calls:
310
if required_version and medium._is_remote_before(required_version):
313
response = self._call(verb, path)
314
except errors.UnknownSmartMethod:
315
if required_version is None:
317
medium._remember_remote_is_before(required_version)
320
if verb == 'BzrDir.open_branch':
321
if response[0] != 'ok':
322
raise errors.UnexpectedSmartServerResponse(response)
323
if response[1] != '':
324
return ('ref', response[1])
326
return ('branch', '')
327
if response[0] not in ('ref', 'branch'):
328
raise errors.UnexpectedSmartServerResponse(response)
331
def _get_tree_branch(self, name=None):
332
"""See BzrDir._get_tree_branch()."""
333
return None, self.open_branch(name=name)
335
def open_branch(self, name=None, unsupported=False,
336
ignore_fallbacks=False):
338
raise NotImplementedError('unsupported flag support not implemented yet.')
339
if self._next_open_branch_result is not None:
340
# See create_branch for details.
341
result = self._next_open_branch_result
342
self._next_open_branch_result = None
344
response = self._get_branch_reference()
345
if response[0] == 'ref':
346
# a branch reference, use the existing BranchReference logic.
347
format = BranchReferenceFormat()
348
return format.open(self, name=name, _found=True,
349
location=response[1], ignore_fallbacks=ignore_fallbacks)
350
branch_format_name = response[1]
351
if not branch_format_name:
352
branch_format_name = None
353
format = RemoteBranchFormat(network_name=branch_format_name)
354
return RemoteBranch(self, self.find_repository(), format=format,
355
setup_stacking=not ignore_fallbacks, name=name)
357
def _open_repo_v1(self, path):
358
verb = 'BzrDir.find_repository'
359
response = self._call(verb, path)
360
if response[0] != 'ok':
361
raise errors.UnexpectedSmartServerResponse(response)
362
# servers that only support the v1 method don't support external
365
repo = self._real_bzrdir.open_repository()
366
response = response + ('no', repo._format.network_name())
367
return response, repo
369
def _open_repo_v2(self, path):
370
verb = 'BzrDir.find_repositoryV2'
371
response = self._call(verb, path)
372
if response[0] != 'ok':
373
raise errors.UnexpectedSmartServerResponse(response)
375
repo = self._real_bzrdir.open_repository()
376
response = response + (repo._format.network_name(),)
377
return response, repo
379
def _open_repo_v3(self, path):
380
verb = 'BzrDir.find_repositoryV3'
381
medium = self._client._medium
382
if medium._is_remote_before((1, 13)):
383
raise errors.UnknownSmartMethod(verb)
385
response = self._call(verb, path)
386
except errors.UnknownSmartMethod:
387
medium._remember_remote_is_before((1, 13))
389
if response[0] != 'ok':
390
raise errors.UnexpectedSmartServerResponse(response)
391
return response, None
393
def open_repository(self):
394
path = self._path_for_remote_call(self._client)
396
for probe in [self._open_repo_v3, self._open_repo_v2,
399
response, real_repo = probe(path)
401
except errors.UnknownSmartMethod:
404
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
405
if response[0] != 'ok':
406
raise errors.UnexpectedSmartServerResponse(response)
407
if len(response) != 6:
408
raise SmartProtocolError('incorrect response length %s' % (response,))
409
if response[1] == '':
410
# repo is at this dir.
411
format = response_tuple_to_repo_format(response[2:])
412
# Used to support creating a real format instance when needed.
413
format._creating_bzrdir = self
414
remote_repo = RemoteRepository(self, format)
415
format._creating_repo = remote_repo
416
if real_repo is not None:
417
remote_repo._set_real_repository(real_repo)
420
raise errors.NoRepositoryPresent(self)
422
def has_workingtree(self):
423
if self._has_working_tree is None:
425
self._has_working_tree = self._real_bzrdir.has_workingtree()
426
return self._has_working_tree
428
def open_workingtree(self, recommend_upgrade=True):
429
if self.has_workingtree():
430
raise errors.NotLocalUrl(self.root_transport)
432
raise errors.NoWorkingTree(self.root_transport.base)
434
def _path_for_remote_call(self, client):
435
"""Return the path to be used for this bzrdir in a remote call."""
436
return client.remote_path_from_transport(self.root_transport)
438
def get_branch_transport(self, branch_format, name=None):
440
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
442
def get_repository_transport(self, repository_format):
444
return self._real_bzrdir.get_repository_transport(repository_format)
446
def get_workingtree_transport(self, workingtree_format):
448
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
450
def can_convert_format(self):
451
"""Upgrading of remote bzrdirs is not supported yet."""
454
def needs_format_conversion(self, format):
455
"""Upgrading of remote bzrdirs is not supported yet."""
458
def clone(self, url, revision_id=None, force_new_repo=False,
459
preserve_stacking=False):
461
return self._real_bzrdir.clone(url, revision_id=revision_id,
462
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
464
def _get_config(self):
465
return RemoteBzrDirConfig(self)
468
class RemoteRepositoryFormat(repository.RepositoryFormat):
469
"""Format for repositories accessed over a _SmartClient.
471
Instances of this repository are represented by RemoteRepository
474
The RemoteRepositoryFormat is parameterized during construction
475
to reflect the capabilities of the real, remote format. Specifically
476
the attributes rich_root_data and supports_tree_reference are set
477
on a per instance basis, and are not set (and should not be) at
480
:ivar _custom_format: If set, a specific concrete repository format that
481
will be used when initializing a repository with this
482
RemoteRepositoryFormat.
483
:ivar _creating_repo: If set, the repository object that this
484
RemoteRepositoryFormat was created for: it can be called into
485
to obtain data like the network name.
488
_matchingbzrdir = RemoteBzrDirFormat()
491
repository.RepositoryFormat.__init__(self)
492
self._custom_format = None
493
self._network_name = None
494
self._creating_bzrdir = None
495
self._supports_chks = None
496
self._supports_external_lookups = None
497
self._supports_tree_reference = None
498
self._rich_root_data = None
501
return "%s(_network_name=%r)" % (self.__class__.__name__,
505
def fast_deltas(self):
507
return self._custom_format.fast_deltas
510
def rich_root_data(self):
511
if self._rich_root_data is None:
513
self._rich_root_data = self._custom_format.rich_root_data
514
return self._rich_root_data
517
def supports_chks(self):
518
if self._supports_chks is None:
520
self._supports_chks = self._custom_format.supports_chks
521
return self._supports_chks
524
def supports_external_lookups(self):
525
if self._supports_external_lookups is None:
527
self._supports_external_lookups = \
528
self._custom_format.supports_external_lookups
529
return self._supports_external_lookups
532
def supports_tree_reference(self):
533
if self._supports_tree_reference is None:
535
self._supports_tree_reference = \
536
self._custom_format.supports_tree_reference
537
return self._supports_tree_reference
539
def _vfs_initialize(self, a_bzrdir, shared):
540
"""Helper for common code in initialize."""
541
if self._custom_format:
542
# Custom format requested
543
result = self._custom_format.initialize(a_bzrdir, shared=shared)
544
elif self._creating_bzrdir is not None:
545
# Use the format that the repository we were created to back
547
prior_repo = self._creating_bzrdir.open_repository()
548
prior_repo._ensure_real()
549
result = prior_repo._real_repository._format.initialize(
550
a_bzrdir, shared=shared)
552
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
553
# support remote initialization.
554
# We delegate to a real object at this point (as RemoteBzrDir
555
# delegate to the repository format which would lead to infinite
556
# recursion if we just called a_bzrdir.create_repository.
557
a_bzrdir._ensure_real()
558
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
559
if not isinstance(result, RemoteRepository):
560
return self.open(a_bzrdir)
564
def initialize(self, a_bzrdir, shared=False):
565
# Being asked to create on a non RemoteBzrDir:
566
if not isinstance(a_bzrdir, RemoteBzrDir):
567
return self._vfs_initialize(a_bzrdir, shared)
568
medium = a_bzrdir._client._medium
569
if medium._is_remote_before((1, 13)):
570
return self._vfs_initialize(a_bzrdir, shared)
571
# Creating on a remote bzr dir.
572
# 1) get the network name to use.
573
if self._custom_format:
574
network_name = self._custom_format.network_name()
575
elif self._network_name:
576
network_name = self._network_name
578
# Select the current bzrlib default and ask for that.
579
reference_bzrdir_format = bzrdir.format_registry.get('default')()
580
reference_format = reference_bzrdir_format.repository_format
581
network_name = reference_format.network_name()
582
# 2) try direct creation via RPC
583
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
584
verb = 'BzrDir.create_repository'
590
response = a_bzrdir._call(verb, path, network_name, shared_str)
591
except errors.UnknownSmartMethod:
592
# Fallback - use vfs methods
593
medium._remember_remote_is_before((1, 13))
594
return self._vfs_initialize(a_bzrdir, shared)
596
# Turn the response into a RemoteRepository object.
597
format = response_tuple_to_repo_format(response[1:])
598
# Used to support creating a real format instance when needed.
599
format._creating_bzrdir = a_bzrdir
600
remote_repo = RemoteRepository(a_bzrdir, format)
601
format._creating_repo = remote_repo
604
def open(self, a_bzrdir):
605
if not isinstance(a_bzrdir, RemoteBzrDir):
606
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
607
return a_bzrdir.open_repository()
609
def _ensure_real(self):
610
if self._custom_format is None:
611
self._custom_format = repository.network_format_registry.get(
615
def _fetch_order(self):
617
return self._custom_format._fetch_order
620
def _fetch_uses_deltas(self):
622
return self._custom_format._fetch_uses_deltas
625
def _fetch_reconcile(self):
627
return self._custom_format._fetch_reconcile
629
def get_format_description(self):
631
return 'Remote: ' + self._custom_format.get_format_description()
633
def __eq__(self, other):
634
return self.__class__ is other.__class__
636
def network_name(self):
637
if self._network_name:
638
return self._network_name
639
self._creating_repo._ensure_real()
640
return self._creating_repo._real_repository._format.network_name()
643
def pack_compresses(self):
645
return self._custom_format.pack_compresses
648
def _serializer(self):
650
return self._custom_format._serializer
653
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
654
controldir.ControlComponent):
655
"""Repository accessed over rpc.
657
For the moment most operations are performed using local transport-backed
661
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
662
"""Create a RemoteRepository instance.
664
:param remote_bzrdir: The bzrdir hosting this repository.
665
:param format: The RemoteFormat object to use.
666
:param real_repository: If not None, a local implementation of the
667
repository logic for the repository, usually accessing the data
669
:param _client: Private testing parameter - override the smart client
670
to be used by the repository.
673
self._real_repository = real_repository
675
self._real_repository = None
676
self.bzrdir = remote_bzrdir
678
self._client = remote_bzrdir._client
680
self._client = _client
681
self._format = format
682
self._lock_mode = None
683
self._lock_token = None
685
self._leave_lock = False
686
# Cache of revision parents; misses are cached during read locks, and
687
# write locks when no _real_repository has been set.
688
self._unstacked_provider = graph.CachingParentsProvider(
689
get_parent_map=self._get_parent_map_rpc)
690
self._unstacked_provider.disable_cache()
692
# These depend on the actual remote format, so force them off for
693
# maximum compatibility. XXX: In future these should depend on the
694
# remote repository instance, but this is irrelevant until we perform
695
# reconcile via an RPC call.
696
self._reconcile_does_inventory_gc = False
697
self._reconcile_fixes_text_parents = False
698
self._reconcile_backsup_inventory = False
699
self.base = self.bzrdir.transport.base
700
# Additional places to query for data.
701
self._fallback_repositories = []
704
def user_transport(self):
705
return self.bzrdir.user_transport
708
def control_transport(self):
709
# XXX: Normally you shouldn't directly get at the remote repository
710
# transport, but I'm not sure it's worth making this method
711
# optional -- mbp 2010-04-21
712
return self.bzrdir.get_repository_transport(None)
715
return "%s(%s)" % (self.__class__.__name__, self.base)
719
def abort_write_group(self, suppress_errors=False):
720
"""Complete a write group on the decorated repository.
722
Smart methods perform operations in a single step so this API
723
is not really applicable except as a compatibility thunk
724
for older plugins that don't use e.g. the CommitBuilder
727
:param suppress_errors: see Repository.abort_write_group.
730
return self._real_repository.abort_write_group(
731
suppress_errors=suppress_errors)
735
"""Decorate the real repository for now.
737
In the long term a full blown network facility is needed to avoid
738
creating a real repository object locally.
741
return self._real_repository.chk_bytes
743
def commit_write_group(self):
744
"""Complete a write group on the decorated repository.
746
Smart methods perform operations in a single step so this API
747
is not really applicable except as a compatibility thunk
748
for older plugins that don't use e.g. the CommitBuilder
752
return self._real_repository.commit_write_group()
754
def resume_write_group(self, tokens):
756
return self._real_repository.resume_write_group(tokens)
758
def suspend_write_group(self):
760
return self._real_repository.suspend_write_group()
762
def get_missing_parent_inventories(self, check_for_missing_texts=True):
764
return self._real_repository.get_missing_parent_inventories(
765
check_for_missing_texts=check_for_missing_texts)
767
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
769
return self._real_repository.get_rev_id_for_revno(
772
def get_rev_id_for_revno(self, revno, known_pair):
773
"""See Repository.get_rev_id_for_revno."""
774
path = self.bzrdir._path_for_remote_call(self._client)
776
if self._client._medium._is_remote_before((1, 17)):
777
return self._get_rev_id_for_revno_vfs(revno, known_pair)
778
response = self._call(
779
'Repository.get_rev_id_for_revno', path, revno, known_pair)
780
except errors.UnknownSmartMethod:
781
self._client._medium._remember_remote_is_before((1, 17))
782
return self._get_rev_id_for_revno_vfs(revno, known_pair)
783
if response[0] == 'ok':
784
return True, response[1]
785
elif response[0] == 'history-incomplete':
786
known_pair = response[1:3]
787
for fallback in self._fallback_repositories:
788
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
793
# Not found in any fallbacks
794
return False, known_pair
796
raise errors.UnexpectedSmartServerResponse(response)
798
def _ensure_real(self):
799
"""Ensure that there is a _real_repository set.
801
Used before calls to self._real_repository.
803
Note that _ensure_real causes many roundtrips to the server which are
804
not desirable, and prevents the use of smart one-roundtrip RPC's to
805
perform complex operations (such as accessing parent data, streaming
806
revisions etc). Adding calls to _ensure_real should only be done when
807
bringing up new functionality, adding fallbacks for smart methods that
808
require a fallback path, and never to replace an existing smart method
809
invocation. If in doubt chat to the bzr network team.
811
if self._real_repository is None:
812
if 'hpssvfs' in debug.debug_flags:
814
warning('VFS Repository access triggered\n%s',
815
''.join(traceback.format_stack()))
816
self._unstacked_provider.missing_keys.clear()
817
self.bzrdir._ensure_real()
818
self._set_real_repository(
819
self.bzrdir._real_bzrdir.open_repository())
821
def _translate_error(self, err, **context):
822
self.bzrdir._translate_error(err, repository=self, **context)
824
def find_text_key_references(self):
825
"""Find the text key references within the repository.
827
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
828
revision_ids. Each altered file-ids has the exact revision_ids that
829
altered it listed explicitly.
830
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
831
to whether they were referred to by the inventory of the
832
revision_id that they contain. The inventory texts from all present
833
revision ids are assessed to generate this report.
836
return self._real_repository.find_text_key_references()
838
def _generate_text_key_index(self):
839
"""Generate a new text key index for the repository.
841
This is an expensive function that will take considerable time to run.
843
:return: A dict mapping (file_id, revision_id) tuples to a list of
844
parents, also (file_id, revision_id) tuples.
847
return self._real_repository._generate_text_key_index()
849
def _get_revision_graph(self, revision_id):
850
"""Private method for using with old (< 1.2) servers to fallback."""
851
if revision_id is None:
853
elif revision.is_null(revision_id):
856
path = self.bzrdir._path_for_remote_call(self._client)
857
response = self._call_expecting_body(
858
'Repository.get_revision_graph', path, revision_id)
859
response_tuple, response_handler = response
860
if response_tuple[0] != 'ok':
861
raise errors.UnexpectedSmartServerResponse(response_tuple)
862
coded = response_handler.read_body_bytes()
864
# no revisions in this repository!
866
lines = coded.split('\n')
869
d = tuple(line.split())
870
revision_graph[d[0]] = d[1:]
872
return revision_graph
875
"""See Repository._get_sink()."""
876
return RemoteStreamSink(self)
878
def _get_source(self, to_format):
879
"""Return a source for streaming from this repository."""
880
return RemoteStreamSource(self, to_format)
883
def has_revision(self, revision_id):
884
"""True if this repository has a copy of the revision."""
885
# Copy of bzrlib.repository.Repository.has_revision
886
return revision_id in self.has_revisions((revision_id,))
889
def has_revisions(self, revision_ids):
890
"""Probe to find out the presence of multiple revisions.
892
:param revision_ids: An iterable of revision_ids.
893
:return: A set of the revision_ids that were present.
895
# Copy of bzrlib.repository.Repository.has_revisions
896
parent_map = self.get_parent_map(revision_ids)
897
result = set(parent_map)
898
if _mod_revision.NULL_REVISION in revision_ids:
899
result.add(_mod_revision.NULL_REVISION)
902
def _has_same_fallbacks(self, other_repo):
903
"""Returns true if the repositories have the same fallbacks."""
904
# XXX: copied from Repository; it should be unified into a base class
905
# <https://bugs.launchpad.net/bzr/+bug/401622>
906
my_fb = self._fallback_repositories
907
other_fb = other_repo._fallback_repositories
908
if len(my_fb) != len(other_fb):
910
for f, g in zip(my_fb, other_fb):
911
if not f.has_same_location(g):
915
def has_same_location(self, other):
916
# TODO: Move to RepositoryBase and unify with the regular Repository
917
# one; unfortunately the tests rely on slightly different behaviour at
918
# present -- mbp 20090710
919
return (self.__class__ is other.__class__ and
920
self.bzrdir.transport.base == other.bzrdir.transport.base)
922
def get_graph(self, other_repository=None):
923
"""Return the graph for this repository format"""
924
parents_provider = self._make_parents_provider(other_repository)
925
return graph.Graph(parents_provider)
928
def get_known_graph_ancestry(self, revision_ids):
929
"""Return the known graph for a set of revision ids and their ancestors.
931
st = static_tuple.StaticTuple
932
revision_keys = [st(r_id).intern() for r_id in revision_ids]
933
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
934
return graph.GraphThunkIdsToKeys(known_graph)
936
def gather_stats(self, revid=None, committers=None):
937
"""See Repository.gather_stats()."""
938
path = self.bzrdir._path_for_remote_call(self._client)
939
# revid can be None to indicate no revisions, not just NULL_REVISION
940
if revid is None or revision.is_null(revid):
944
if committers is None or not committers:
945
fmt_committers = 'no'
947
fmt_committers = 'yes'
948
response_tuple, response_handler = self._call_expecting_body(
949
'Repository.gather_stats', path, fmt_revid, fmt_committers)
950
if response_tuple[0] != 'ok':
951
raise errors.UnexpectedSmartServerResponse(response_tuple)
953
body = response_handler.read_body_bytes()
955
for line in body.split('\n'):
958
key, val_text = line.split(':')
959
if key in ('revisions', 'size', 'committers'):
960
result[key] = int(val_text)
961
elif key in ('firstrev', 'latestrev'):
962
values = val_text.split(' ')[1:]
963
result[key] = (float(values[0]), long(values[1]))
967
def find_branches(self, using=False):
968
"""See Repository.find_branches()."""
969
# should be an API call to the server.
971
return self._real_repository.find_branches(using=using)
973
def get_physical_lock_status(self):
974
"""See Repository.get_physical_lock_status()."""
975
# should be an API call to the server.
977
return self._real_repository.get_physical_lock_status()
979
def is_in_write_group(self):
980
"""Return True if there is an open write group.
982
write groups are only applicable locally for the smart server..
984
if self._real_repository:
985
return self._real_repository.is_in_write_group()
988
return self._lock_count >= 1
991
"""See Repository.is_shared()."""
992
path = self.bzrdir._path_for_remote_call(self._client)
993
response = self._call('Repository.is_shared', path)
994
if response[0] not in ('yes', 'no'):
995
raise SmartProtocolError('unexpected response code %s' % (response,))
996
return response[0] == 'yes'
998
def is_write_locked(self):
999
return self._lock_mode == 'w'
1001
def _warn_if_deprecated(self, branch=None):
1002
# If we have a real repository, the check will be done there, if we
1003
# don't the check will be done remotely.
1006
def lock_read(self):
1007
"""Lock the repository for read operations.
1009
:return: A bzrlib.lock.LogicalLockResult.
1011
# wrong eventually - want a local lock cache context
1012
if not self._lock_mode:
1013
self._note_lock('r')
1014
self._lock_mode = 'r'
1015
self._lock_count = 1
1016
self._unstacked_provider.enable_cache(cache_misses=True)
1017
if self._real_repository is not None:
1018
self._real_repository.lock_read()
1019
for repo in self._fallback_repositories:
1022
self._lock_count += 1
1023
return lock.LogicalLockResult(self.unlock)
1025
def _remote_lock_write(self, token):
1026
path = self.bzrdir._path_for_remote_call(self._client)
1029
err_context = {'token': token}
1030
response = self._call('Repository.lock_write', path, token,
1032
if response[0] == 'ok':
1033
ok, token = response
1036
raise errors.UnexpectedSmartServerResponse(response)
1038
def lock_write(self, token=None, _skip_rpc=False):
1039
if not self._lock_mode:
1040
self._note_lock('w')
1042
if self._lock_token is not None:
1043
if token != self._lock_token:
1044
raise errors.TokenMismatch(token, self._lock_token)
1045
self._lock_token = token
1047
self._lock_token = self._remote_lock_write(token)
1048
# if self._lock_token is None, then this is something like packs or
1049
# svn where we don't get to lock the repo, or a weave style repository
1050
# where we cannot lock it over the wire and attempts to do so will
1052
if self._real_repository is not None:
1053
self._real_repository.lock_write(token=self._lock_token)
1054
if token is not None:
1055
self._leave_lock = True
1057
self._leave_lock = False
1058
self._lock_mode = 'w'
1059
self._lock_count = 1
1060
cache_misses = self._real_repository is None
1061
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1062
for repo in self._fallback_repositories:
1063
# Writes don't affect fallback repos
1065
elif self._lock_mode == 'r':
1066
raise errors.ReadOnlyError(self)
1068
self._lock_count += 1
1069
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1071
def leave_lock_in_place(self):
1072
if not self._lock_token:
1073
raise NotImplementedError(self.leave_lock_in_place)
1074
self._leave_lock = True
1076
def dont_leave_lock_in_place(self):
1077
if not self._lock_token:
1078
raise NotImplementedError(self.dont_leave_lock_in_place)
1079
self._leave_lock = False
1081
def _set_real_repository(self, repository):
1082
"""Set the _real_repository for this repository.
1084
:param repository: The repository to fallback to for non-hpss
1085
implemented operations.
1087
if self._real_repository is not None:
1088
# Replacing an already set real repository.
1089
# We cannot do this [currently] if the repository is locked -
1090
# synchronised state might be lost.
1091
if self.is_locked():
1092
raise AssertionError('_real_repository is already set')
1093
if isinstance(repository, RemoteRepository):
1094
raise AssertionError()
1095
self._real_repository = repository
1096
# three code paths happen here:
1097
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1098
# up stacking. In this case self._fallback_repositories is [], and the
1099
# real repo is already setup. Preserve the real repo and
1100
# RemoteRepository.add_fallback_repository will avoid adding
1102
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1103
# ensure_real is triggered from a branch, the real repository to
1104
# set already has a matching list with separate instances, but
1105
# as they are also RemoteRepositories we don't worry about making the
1106
# lists be identical.
1107
# 3) new servers, RemoteRepository.ensure_real is triggered before
1108
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1109
# and need to populate it.
1110
if (self._fallback_repositories and
1111
len(self._real_repository._fallback_repositories) !=
1112
len(self._fallback_repositories)):
1113
if len(self._real_repository._fallback_repositories):
1114
raise AssertionError(
1115
"cannot cleanly remove existing _fallback_repositories")
1116
for fb in self._fallback_repositories:
1117
self._real_repository.add_fallback_repository(fb)
1118
if self._lock_mode == 'w':
1119
# if we are already locked, the real repository must be able to
1120
# acquire the lock with our token.
1121
self._real_repository.lock_write(self._lock_token)
1122
elif self._lock_mode == 'r':
1123
self._real_repository.lock_read()
1125
def start_write_group(self):
1126
"""Start a write group on the decorated repository.
1128
Smart methods perform operations in a single step so this API
1129
is not really applicable except as a compatibility thunk
1130
for older plugins that don't use e.g. the CommitBuilder
1134
return self._real_repository.start_write_group()
1136
def _unlock(self, token):
1137
path = self.bzrdir._path_for_remote_call(self._client)
1139
# with no token the remote repository is not persistently locked.
1141
err_context = {'token': token}
1142
response = self._call('Repository.unlock', path, token,
1144
if response == ('ok',):
1147
raise errors.UnexpectedSmartServerResponse(response)
1149
@only_raises(errors.LockNotHeld, errors.LockBroken)
1151
if not self._lock_count:
1152
return lock.cant_unlock_not_held(self)
1153
self._lock_count -= 1
1154
if self._lock_count > 0:
1156
self._unstacked_provider.disable_cache()
1157
old_mode = self._lock_mode
1158
self._lock_mode = None
1160
# The real repository is responsible at present for raising an
1161
# exception if it's in an unfinished write group. However, it
1162
# normally will *not* actually remove the lock from disk - that's
1163
# done by the server on receiving the Repository.unlock call.
1164
# This is just to let the _real_repository stay up to date.
1165
if self._real_repository is not None:
1166
self._real_repository.unlock()
1168
# The rpc-level lock should be released even if there was a
1169
# problem releasing the vfs-based lock.
1171
# Only write-locked repositories need to make a remote method
1172
# call to perform the unlock.
1173
old_token = self._lock_token
1174
self._lock_token = None
1175
if not self._leave_lock:
1176
self._unlock(old_token)
1177
# Fallbacks are always 'lock_read()' so we don't pay attention to
1179
for repo in self._fallback_repositories:
1182
def break_lock(self):
1183
# should hand off to the network
1185
return self._real_repository.break_lock()
1187
def _get_tarball(self, compression):
1188
"""Return a TemporaryFile containing a repository tarball.
1190
Returns None if the server does not support sending tarballs.
1193
path = self.bzrdir._path_for_remote_call(self._client)
1195
response, protocol = self._call_expecting_body(
1196
'Repository.tarball', path, compression)
1197
except errors.UnknownSmartMethod:
1198
protocol.cancel_read_body()
1200
if response[0] == 'ok':
1201
# Extract the tarball and return it
1202
t = tempfile.NamedTemporaryFile()
1203
# TODO: rpc layer should read directly into it...
1204
t.write(protocol.read_body_bytes())
1207
raise errors.UnexpectedSmartServerResponse(response)
1209
def sprout(self, to_bzrdir, revision_id=None):
1210
# TODO: Option to control what format is created?
1212
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1214
dest_repo.fetch(self, revision_id=revision_id)
1217
### These methods are just thin shims to the VFS object for now.
1219
def revision_tree(self, revision_id):
1221
return self._real_repository.revision_tree(revision_id)
1223
def get_serializer_format(self):
1225
return self._real_repository.get_serializer_format()
1227
def get_commit_builder(self, branch, parents, config, timestamp=None,
1228
timezone=None, committer=None, revprops=None,
1230
# FIXME: It ought to be possible to call this without immediately
1231
# triggering _ensure_real. For now it's the easiest thing to do.
1233
real_repo = self._real_repository
1234
builder = real_repo.get_commit_builder(branch, parents,
1235
config, timestamp=timestamp, timezone=timezone,
1236
committer=committer, revprops=revprops, revision_id=revision_id)
1239
def add_fallback_repository(self, repository):
1240
"""Add a repository to use for looking up data not held locally.
1242
:param repository: A repository.
1244
if not self._format.supports_external_lookups:
1245
raise errors.UnstackableRepositoryFormat(
1246
self._format.network_name(), self.base)
1247
# We need to accumulate additional repositories here, to pass them in
1250
if self.is_locked():
1251
# We will call fallback.unlock() when we transition to the unlocked
1252
# state, so always add a lock here. If a caller passes us a locked
1253
# repository, they are responsible for unlocking it later.
1254
repository.lock_read()
1255
self._check_fallback_repository(repository)
1256
self._fallback_repositories.append(repository)
1257
# If self._real_repository was parameterised already (e.g. because a
1258
# _real_branch had its get_stacked_on_url method called), then the
1259
# repository to be added may already be in the _real_repositories list.
1260
if self._real_repository is not None:
1261
fallback_locations = [repo.user_url for repo in
1262
self._real_repository._fallback_repositories]
1263
if repository.user_url not in fallback_locations:
1264
self._real_repository.add_fallback_repository(repository)
1266
def _check_fallback_repository(self, repository):
1267
"""Check that this repository can fallback to repository safely.
1269
Raise an error if not.
1271
:param repository: A repository to fallback to.
1273
return _mod_repository.InterRepository._assert_same_model(
1276
def add_inventory(self, revid, inv, parents):
1278
return self._real_repository.add_inventory(revid, inv, parents)
1280
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1281
parents, basis_inv=None, propagate_caches=False):
1283
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1284
delta, new_revision_id, parents, basis_inv=basis_inv,
1285
propagate_caches=propagate_caches)
1287
def add_revision(self, rev_id, rev, inv=None, config=None):
1289
return self._real_repository.add_revision(
1290
rev_id, rev, inv=inv, config=config)
1293
def get_inventory(self, revision_id):
1295
return self._real_repository.get_inventory(revision_id)
1297
def iter_inventories(self, revision_ids, ordering=None):
1299
return self._real_repository.iter_inventories(revision_ids, ordering)
1302
def get_revision(self, revision_id):
1304
return self._real_repository.get_revision(revision_id)
1306
def get_transaction(self):
1308
return self._real_repository.get_transaction()
1311
def clone(self, a_bzrdir, revision_id=None):
1313
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1315
def make_working_trees(self):
1316
"""See Repository.make_working_trees"""
1318
return self._real_repository.make_working_trees()
1320
def refresh_data(self):
1321
"""Re-read any data needed to synchronise with disk.
1323
This method is intended to be called after another repository instance
1324
(such as one used by a smart server) has inserted data into the
1325
repository. On all repositories this will work outside of write groups.
1326
Some repository formats (pack and newer for bzrlib native formats)
1327
support refresh_data inside write groups. If called inside a write
1328
group on a repository that does not support refreshing in a write group
1329
IsInWriteGroupError will be raised.
1331
if self._real_repository is not None:
1332
self._real_repository.refresh_data()
1334
def revision_ids_to_search_result(self, result_set):
1335
"""Convert a set of revision ids to a graph SearchResult."""
1336
result_parents = set()
1337
for parents in self.get_graph().get_parent_map(
1338
result_set).itervalues():
1339
result_parents.update(parents)
1340
included_keys = result_set.intersection(result_parents)
1341
start_keys = result_set.difference(included_keys)
1342
exclude_keys = result_parents.difference(result_set)
1343
result = graph.SearchResult(start_keys, exclude_keys,
1344
len(result_set), result_set)
1348
def search_missing_revision_ids(self, other,
1349
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1350
find_ghosts=True, revision_ids=None, if_present_ids=None):
1351
"""Return the revision ids that other has that this does not.
1353
These are returned in topological order.
1355
revision_id: only return revision ids included by revision_id.
1357
if symbol_versioning.deprecated_passed(revision_id):
1358
symbol_versioning.warn(
1359
'search_missing_revision_ids(revision_id=...) was '
1360
'deprecated in 2.4. Use revision_ids=[...] instead.',
1361
DeprecationWarning, stacklevel=2)
1362
if revision_ids is not None:
1363
raise AssertionError(
1364
'revision_ids is mutually exclusive with revision_id')
1365
if revision_id is not None:
1366
revision_ids = [revision_id]
1367
inter_repo = repository.InterRepository.get(other, self)
1368
return inter_repo.search_missing_revision_ids(
1369
find_ghosts=find_ghosts, revision_ids=revision_ids,
1370
if_present_ids=if_present_ids)
1372
def fetch(self, source, revision_id=None, find_ghosts=False,
1374
# No base implementation to use as RemoteRepository is not a subclass
1375
# of Repository; so this is a copy of Repository.fetch().
1376
if fetch_spec is not None and revision_id is not None:
1377
raise AssertionError(
1378
"fetch_spec and revision_id are mutually exclusive.")
1379
if self.is_in_write_group():
1380
raise errors.InternalBzrError(
1381
"May not fetch while in a write group.")
1382
# fast path same-url fetch operations
1383
if (self.has_same_location(source)
1384
and fetch_spec is None
1385
and self._has_same_fallbacks(source)):
1386
# check that last_revision is in 'from' and then return a
1388
if (revision_id is not None and
1389
not revision.is_null(revision_id)):
1390
self.get_revision(revision_id)
1392
# if there is no specific appropriate InterRepository, this will get
1393
# the InterRepository base class, which raises an
1394
# IncompatibleRepositories when asked to fetch.
1395
inter = repository.InterRepository.get(source, self)
1396
return inter.fetch(revision_id=revision_id,
1397
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1399
def create_bundle(self, target, base, fileobj, format=None):
1401
self._real_repository.create_bundle(target, base, fileobj, format)
1404
def get_ancestry(self, revision_id, topo_sorted=True):
1406
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1408
def fileids_altered_by_revision_ids(self, revision_ids):
1410
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1412
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1414
return self._real_repository._get_versioned_file_checker(
1415
revisions, revision_versions_cache)
1417
def iter_files_bytes(self, desired_files):
1418
"""See Repository.iter_file_bytes.
1421
return self._real_repository.iter_files_bytes(desired_files)
1423
def get_parent_map(self, revision_ids):
1424
"""See bzrlib.Graph.get_parent_map()."""
1425
return self._make_parents_provider().get_parent_map(revision_ids)
1427
def _get_parent_map_rpc(self, keys):
1428
"""Helper for get_parent_map that performs the RPC."""
1429
medium = self._client._medium
1430
if medium._is_remote_before((1, 2)):
1431
# We already found out that the server can't understand
1432
# Repository.get_parent_map requests, so just fetch the whole
1435
# Note that this reads the whole graph, when only some keys are
1436
# wanted. On this old server there's no way (?) to get them all
1437
# in one go, and the user probably will have seen a warning about
1438
# the server being old anyhow.
1439
rg = self._get_revision_graph(None)
1440
# There is an API discrepancy between get_parent_map and
1441
# get_revision_graph. Specifically, a "key:()" pair in
1442
# get_revision_graph just means a node has no parents. For
1443
# "get_parent_map" it means the node is a ghost. So fix up the
1444
# graph to correct this.
1445
# https://bugs.launchpad.net/bzr/+bug/214894
1446
# There is one other "bug" which is that ghosts in
1447
# get_revision_graph() are not returned at all. But we won't worry
1448
# about that for now.
1449
for node_id, parent_ids in rg.iteritems():
1450
if parent_ids == ():
1451
rg[node_id] = (NULL_REVISION,)
1452
rg[NULL_REVISION] = ()
1457
raise ValueError('get_parent_map(None) is not valid')
1458
if NULL_REVISION in keys:
1459
keys.discard(NULL_REVISION)
1460
found_parents = {NULL_REVISION:()}
1462
return found_parents
1465
# TODO(Needs analysis): We could assume that the keys being requested
1466
# from get_parent_map are in a breadth first search, so typically they
1467
# will all be depth N from some common parent, and we don't have to
1468
# have the server iterate from the root parent, but rather from the
1469
# keys we're searching; and just tell the server the keyspace we
1470
# already have; but this may be more traffic again.
1472
# Transform self._parents_map into a search request recipe.
1473
# TODO: Manage this incrementally to avoid covering the same path
1474
# repeatedly. (The server will have to on each request, but the less
1475
# work done the better).
1477
# Negative caching notes:
1478
# new server sends missing when a request including the revid
1479
# 'include-missing:' is present in the request.
1480
# missing keys are serialised as missing:X, and we then call
1481
# provider.note_missing(X) for-all X
1482
parents_map = self._unstacked_provider.get_cached_map()
1483
if parents_map is None:
1484
# Repository is not locked, so there's no cache.
1486
# start_set is all the keys in the cache
1487
start_set = set(parents_map)
1488
# result set is all the references to keys in the cache
1489
result_parents = set()
1490
for parents in parents_map.itervalues():
1491
result_parents.update(parents)
1492
stop_keys = result_parents.difference(start_set)
1493
# We don't need to send ghosts back to the server as a position to
1495
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1496
key_count = len(parents_map)
1497
if (NULL_REVISION in result_parents
1498
and NULL_REVISION in self._unstacked_provider.missing_keys):
1499
# If we pruned NULL_REVISION from the stop_keys because it's also
1500
# in our cache of "missing" keys we need to increment our key count
1501
# by 1, because the reconsitituted SearchResult on the server will
1502
# still consider NULL_REVISION to be an included key.
1504
included_keys = start_set.intersection(result_parents)
1505
start_set.difference_update(included_keys)
1506
recipe = ('manual', start_set, stop_keys, key_count)
1507
body = self._serialise_search_recipe(recipe)
1508
path = self.bzrdir._path_for_remote_call(self._client)
1510
if type(key) is not str:
1512
"key %r not a plain string" % (key,))
1513
verb = 'Repository.get_parent_map'
1514
args = (path, 'include-missing:') + tuple(keys)
1516
response = self._call_with_body_bytes_expecting_body(
1518
except errors.UnknownSmartMethod:
1519
# Server does not support this method, so get the whole graph.
1520
# Worse, we have to force a disconnection, because the server now
1521
# doesn't realise it has a body on the wire to consume, so the
1522
# only way to recover is to abandon the connection.
1524
'Server is too old for fast get_parent_map, reconnecting. '
1525
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1527
# To avoid having to disconnect repeatedly, we keep track of the
1528
# fact the server doesn't understand remote methods added in 1.2.
1529
medium._remember_remote_is_before((1, 2))
1530
# Recurse just once and we should use the fallback code.
1531
return self._get_parent_map_rpc(keys)
1532
response_tuple, response_handler = response
1533
if response_tuple[0] not in ['ok']:
1534
response_handler.cancel_read_body()
1535
raise errors.UnexpectedSmartServerResponse(response_tuple)
1536
if response_tuple[0] == 'ok':
1537
coded = bz2.decompress(response_handler.read_body_bytes())
1539
# no revisions found
1541
lines = coded.split('\n')
1544
d = tuple(line.split())
1546
revision_graph[d[0]] = d[1:]
1549
if d[0].startswith('missing:'):
1551
self._unstacked_provider.note_missing_key(revid)
1553
# no parents - so give the Graph result
1555
revision_graph[d[0]] = (NULL_REVISION,)
1556
return revision_graph
1559
def get_signature_text(self, revision_id):
1561
return self._real_repository.get_signature_text(revision_id)
1564
def _get_inventory_xml(self, revision_id):
1566
return self._real_repository._get_inventory_xml(revision_id)
1568
def reconcile(self, other=None, thorough=False):
1570
return self._real_repository.reconcile(other=other, thorough=thorough)
1572
def all_revision_ids(self):
1574
return self._real_repository.all_revision_ids()
1577
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1579
return self._real_repository.get_deltas_for_revisions(revisions,
1580
specific_fileids=specific_fileids)
1583
def get_revision_delta(self, revision_id, specific_fileids=None):
1585
return self._real_repository.get_revision_delta(revision_id,
1586
specific_fileids=specific_fileids)
1589
def revision_trees(self, revision_ids):
1591
return self._real_repository.revision_trees(revision_ids)
1594
def get_revision_reconcile(self, revision_id):
1596
return self._real_repository.get_revision_reconcile(revision_id)
1599
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1601
return self._real_repository.check(revision_ids=revision_ids,
1602
callback_refs=callback_refs, check_repo=check_repo)
1604
def copy_content_into(self, destination, revision_id=None):
1606
return self._real_repository.copy_content_into(
1607
destination, revision_id=revision_id)
1609
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1610
# get a tarball of the remote repository, and copy from that into the
1612
from bzrlib import osutils
1614
# TODO: Maybe a progress bar while streaming the tarball?
1615
note("Copying repository content as tarball...")
1616
tar_file = self._get_tarball('bz2')
1617
if tar_file is None:
1619
destination = to_bzrdir.create_repository()
1621
tar = tarfile.open('repository', fileobj=tar_file,
1623
tmpdir = osutils.mkdtemp()
1625
_extract_tar(tar, tmpdir)
1626
tmp_bzrdir = BzrDir.open(tmpdir)
1627
tmp_repo = tmp_bzrdir.open_repository()
1628
tmp_repo.copy_content_into(destination, revision_id)
1630
osutils.rmtree(tmpdir)
1634
# TODO: Suggestion from john: using external tar is much faster than
1635
# python's tarfile library, but it may not work on windows.
1638
def inventories(self):
1639
"""Decorate the real repository for now.
1641
In the long term a full blown network facility is needed to
1642
avoid creating a real repository object locally.
1645
return self._real_repository.inventories
1648
def pack(self, hint=None, clean_obsolete_packs=False):
1649
"""Compress the data within the repository.
1651
This is not currently implemented within the smart server.
1654
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1657
def revisions(self):
1658
"""Decorate the real repository for now.
1660
In the short term this should become a real object to intercept graph
1663
In the long term a full blown network facility is needed.
1666
return self._real_repository.revisions
1668
def set_make_working_trees(self, new_value):
1670
new_value_str = "True"
1672
new_value_str = "False"
1673
path = self.bzrdir._path_for_remote_call(self._client)
1675
response = self._call(
1676
'Repository.set_make_working_trees', path, new_value_str)
1677
except errors.UnknownSmartMethod:
1679
self._real_repository.set_make_working_trees(new_value)
1681
if response[0] != 'ok':
1682
raise errors.UnexpectedSmartServerResponse(response)
1685
def signatures(self):
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.signatures
1695
def sign_revision(self, revision_id, gpg_strategy):
1697
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1701
"""Decorate the real repository for now.
1703
In the long term a full blown network facility is needed to avoid
1704
creating a real repository object locally.
1707
return self._real_repository.texts
1710
def get_revisions(self, revision_ids):
1712
return self._real_repository.get_revisions(revision_ids)
1714
def supports_rich_root(self):
1715
return self._format.rich_root_data
1717
def iter_reverse_revision_history(self, revision_id):
1719
return self._real_repository.iter_reverse_revision_history(revision_id)
1722
def _serializer(self):
1723
return self._format._serializer
1725
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1727
return self._real_repository.store_revision_signature(
1728
gpg_strategy, plaintext, revision_id)
1730
def add_signature_text(self, revision_id, signature):
1732
return self._real_repository.add_signature_text(revision_id, signature)
1734
def has_signature_for_revision_id(self, revision_id):
1736
return self._real_repository.has_signature_for_revision_id(revision_id)
1738
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1740
return self._real_repository.item_keys_introduced_by(revision_ids,
1741
_files_pb=_files_pb)
1743
def revision_graph_can_have_wrong_parents(self):
1744
# The answer depends on the remote repo format.
1746
return self._real_repository.revision_graph_can_have_wrong_parents()
1748
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1750
return self._real_repository._find_inconsistent_revision_parents(
1753
def _check_for_inconsistent_revision_parents(self):
1755
return self._real_repository._check_for_inconsistent_revision_parents()
1757
def _make_parents_provider(self, other=None):
1758
providers = [self._unstacked_provider]
1759
if other is not None:
1760
providers.insert(0, other)
1761
providers.extend(r._make_parents_provider() for r in
1762
self._fallback_repositories)
1763
return graph.StackedParentsProvider(providers)
1765
def _serialise_search_recipe(self, recipe):
1766
"""Serialise a graph search recipe.
1768
:param recipe: A search recipe (start, stop, count).
1769
:return: Serialised bytes.
1771
start_keys = ' '.join(recipe[1])
1772
stop_keys = ' '.join(recipe[2])
1773
count = str(recipe[3])
1774
return '\n'.join((start_keys, stop_keys, count))
1776
def _serialise_search_result(self, search_result):
1777
parts = search_result.get_network_struct()
1778
return '\n'.join(parts)
1781
path = self.bzrdir._path_for_remote_call(self._client)
1783
response = self._call('PackRepository.autopack', path)
1784
except errors.UnknownSmartMethod:
1786
self._real_repository._pack_collection.autopack()
1789
if response[0] != 'ok':
1790
raise errors.UnexpectedSmartServerResponse(response)
1793
class RemoteStreamSink(repository.StreamSink):
1795
def _insert_real(self, stream, src_format, resume_tokens):
1796
self.target_repo._ensure_real()
1797
sink = self.target_repo._real_repository._get_sink()
1798
result = sink.insert_stream(stream, src_format, resume_tokens)
1800
self.target_repo.autopack()
1803
def insert_stream(self, stream, src_format, resume_tokens):
1804
target = self.target_repo
1805
target._unstacked_provider.missing_keys.clear()
1806
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1807
if target._lock_token:
1808
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1809
lock_args = (target._lock_token or '',)
1811
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1813
client = target._client
1814
medium = client._medium
1815
path = target.bzrdir._path_for_remote_call(client)
1816
# Probe for the verb to use with an empty stream before sending the
1817
# real stream to it. We do this both to avoid the risk of sending a
1818
# large request that is then rejected, and because we don't want to
1819
# implement a way to buffer, rewind, or restart the stream.
1821
for verb, required_version in candidate_calls:
1822
if medium._is_remote_before(required_version):
1825
# We've already done the probing (and set _is_remote_before) on
1826
# a previous insert.
1829
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1831
response = client.call_with_body_stream(
1832
(verb, path, '') + lock_args, byte_stream)
1833
except errors.UnknownSmartMethod:
1834
medium._remember_remote_is_before(required_version)
1840
return self._insert_real(stream, src_format, resume_tokens)
1841
self._last_inv_record = None
1842
self._last_substream = None
1843
if required_version < (1, 19):
1844
# Remote side doesn't support inventory deltas. Wrap the stream to
1845
# make sure we don't send any. If the stream contains inventory
1846
# deltas we'll interrupt the smart insert_stream request and
1848
stream = self._stop_stream_if_inventory_delta(stream)
1849
byte_stream = smart_repo._stream_to_byte_stream(
1851
resume_tokens = ' '.join(resume_tokens)
1852
response = client.call_with_body_stream(
1853
(verb, path, resume_tokens) + lock_args, byte_stream)
1854
if response[0][0] not in ('ok', 'missing-basis'):
1855
raise errors.UnexpectedSmartServerResponse(response)
1856
if self._last_substream is not None:
1857
# The stream included an inventory-delta record, but the remote
1858
# side isn't new enough to support them. So we need to send the
1859
# rest of the stream via VFS.
1860
self.target_repo.refresh_data()
1861
return self._resume_stream_with_vfs(response, src_format)
1862
if response[0][0] == 'missing-basis':
1863
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1864
resume_tokens = tokens
1865
return resume_tokens, set(missing_keys)
1867
self.target_repo.refresh_data()
1870
def _resume_stream_with_vfs(self, response, src_format):
1871
"""Resume sending a stream via VFS, first resending the record and
1872
substream that couldn't be sent via an insert_stream verb.
1874
if response[0][0] == 'missing-basis':
1875
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1876
# Ignore missing_keys, we haven't finished inserting yet
1879
def resume_substream():
1880
# Yield the substream that was interrupted.
1881
for record in self._last_substream:
1883
self._last_substream = None
1884
def resume_stream():
1885
# Finish sending the interrupted substream
1886
yield ('inventory-deltas', resume_substream())
1887
# Then simply continue sending the rest of the stream.
1888
for substream_kind, substream in self._last_stream:
1889
yield substream_kind, substream
1890
return self._insert_real(resume_stream(), src_format, tokens)
1892
def _stop_stream_if_inventory_delta(self, stream):
1893
"""Normally this just lets the original stream pass-through unchanged.
1895
However if any 'inventory-deltas' substream occurs it will stop
1896
streaming, and store the interrupted substream and stream in
1897
self._last_substream and self._last_stream so that the stream can be
1898
resumed by _resume_stream_with_vfs.
1901
stream_iter = iter(stream)
1902
for substream_kind, substream in stream_iter:
1903
if substream_kind == 'inventory-deltas':
1904
self._last_substream = substream
1905
self._last_stream = stream_iter
1908
yield substream_kind, substream
1911
class RemoteStreamSource(repository.StreamSource):
1912
"""Stream data from a remote server."""
1914
def get_stream(self, search):
1915
if (self.from_repository._fallback_repositories and
1916
self.to_format._fetch_order == 'topological'):
1917
return self._real_stream(self.from_repository, search)
1920
repos = [self.from_repository]
1926
repos.extend(repo._fallback_repositories)
1927
sources.append(repo)
1928
return self.missing_parents_chain(search, sources)
1930
def get_stream_for_missing_keys(self, missing_keys):
1931
self.from_repository._ensure_real()
1932
real_repo = self.from_repository._real_repository
1933
real_source = real_repo._get_source(self.to_format)
1934
return real_source.get_stream_for_missing_keys(missing_keys)
1936
def _real_stream(self, repo, search):
1937
"""Get a stream for search from repo.
1939
This never called RemoteStreamSource.get_stream, and is a heler
1940
for RemoteStreamSource._get_stream to allow getting a stream
1941
reliably whether fallback back because of old servers or trying
1942
to stream from a non-RemoteRepository (which the stacked support
1945
source = repo._get_source(self.to_format)
1946
if isinstance(source, RemoteStreamSource):
1948
source = repo._real_repository._get_source(self.to_format)
1949
return source.get_stream(search)
1951
def _get_stream(self, repo, search):
1952
"""Core worker to get a stream from repo for search.
1954
This is used by both get_stream and the stacking support logic. It
1955
deliberately gets a stream for repo which does not need to be
1956
self.from_repository. In the event that repo is not Remote, or
1957
cannot do a smart stream, a fallback is made to the generic
1958
repository._get_stream() interface, via self._real_stream.
1960
In the event of stacking, streams from _get_stream will not
1961
contain all the data for search - this is normal (see get_stream).
1963
:param repo: A repository.
1964
:param search: A search.
1966
# Fallbacks may be non-smart
1967
if not isinstance(repo, RemoteRepository):
1968
return self._real_stream(repo, search)
1969
client = repo._client
1970
medium = client._medium
1971
path = repo.bzrdir._path_for_remote_call(client)
1972
search_bytes = repo._serialise_search_result(search)
1973
args = (path, self.to_format.network_name())
1975
('Repository.get_stream_1.19', (1, 19)),
1976
('Repository.get_stream', (1, 13))]
1979
for verb, version in candidate_verbs:
1980
if medium._is_remote_before(version):
1983
response = repo._call_with_body_bytes_expecting_body(
1984
verb, args, search_bytes)
1985
except errors.UnknownSmartMethod:
1986
medium._remember_remote_is_before(version)
1987
except errors.UnknownErrorFromSmartServer, e:
1988
if isinstance(search, graph.EverythingResult):
1989
error_verb = e.error_from_smart_server.error_verb
1990
if error_verb == 'BadSearch':
1991
# Pre-2.4 servers don't support this sort of search.
1992
# XXX: perhaps falling back to VFS on BadSearch is a
1993
# good idea in general? It might provide a little bit
1994
# of protection against client-side bugs.
1995
medium._remember_remote_is_before((2, 4))
1999
response_tuple, response_handler = response
2003
return self._real_stream(repo, search)
2004
if response_tuple[0] != 'ok':
2005
raise errors.UnexpectedSmartServerResponse(response_tuple)
2006
byte_stream = response_handler.read_streamed_body()
2007
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2008
self._record_counter)
2009
if src_format.network_name() != repo._format.network_name():
2010
raise AssertionError(
2011
"Mismatched RemoteRepository and stream src %r, %r" % (
2012
src_format.network_name(), repo._format.network_name()))
2015
def missing_parents_chain(self, search, sources):
2016
"""Chain multiple streams together to handle stacking.
2018
:param search: The overall search to satisfy with streams.
2019
:param sources: A list of Repository objects to query.
2021
self.from_serialiser = self.from_repository._format._serializer
2022
self.seen_revs = set()
2023
self.referenced_revs = set()
2024
# If there are heads in the search, or the key count is > 0, we are not
2026
while not search.is_empty() and len(sources) > 1:
2027
source = sources.pop(0)
2028
stream = self._get_stream(source, search)
2029
for kind, substream in stream:
2030
if kind != 'revisions':
2031
yield kind, substream
2033
yield kind, self.missing_parents_rev_handler(substream)
2034
search = search.refine(self.seen_revs, self.referenced_revs)
2035
self.seen_revs = set()
2036
self.referenced_revs = set()
2037
if not search.is_empty():
2038
for kind, stream in self._get_stream(sources[0], search):
2041
def missing_parents_rev_handler(self, substream):
2042
for content in substream:
2043
revision_bytes = content.get_bytes_as('fulltext')
2044
revision = self.from_serialiser.read_revision_from_string(
2046
self.seen_revs.add(content.key[-1])
2047
self.referenced_revs.update(revision.parent_ids)
2051
class RemoteBranchLockableFiles(LockableFiles):
2052
"""A 'LockableFiles' implementation that talks to a smart server.
2054
This is not a public interface class.
2057
def __init__(self, bzrdir, _client):
2058
self.bzrdir = bzrdir
2059
self._client = _client
2060
self._need_find_modes = True
2061
LockableFiles.__init__(
2062
self, bzrdir.get_branch_transport(None),
2063
'lock', lockdir.LockDir)
2065
def _find_modes(self):
2066
# RemoteBranches don't let the client set the mode of control files.
2067
self._dir_mode = None
2068
self._file_mode = None
2071
class RemoteBranchFormat(branch.BranchFormat):
2073
def __init__(self, network_name=None):
2074
super(RemoteBranchFormat, self).__init__()
2075
self._matchingbzrdir = RemoteBzrDirFormat()
2076
self._matchingbzrdir.set_branch_format(self)
2077
self._custom_format = None
2078
self._network_name = network_name
2080
def __eq__(self, other):
2081
return (isinstance(other, RemoteBranchFormat) and
2082
self.__dict__ == other.__dict__)
2084
def _ensure_real(self):
2085
if self._custom_format is None:
2086
self._custom_format = branch.network_format_registry.get(
2089
def get_format_description(self):
2091
return 'Remote: ' + self._custom_format.get_format_description()
2093
def network_name(self):
2094
return self._network_name
2096
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2097
return a_bzrdir.open_branch(name=name,
2098
ignore_fallbacks=ignore_fallbacks)
2100
def _vfs_initialize(self, a_bzrdir, name):
2101
# Initialisation when using a local bzrdir object, or a non-vfs init
2102
# method is not available on the server.
2103
# self._custom_format is always set - the start of initialize ensures
2105
if isinstance(a_bzrdir, RemoteBzrDir):
2106
a_bzrdir._ensure_real()
2107
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2110
# We assume the bzrdir is parameterised; it may not be.
2111
result = self._custom_format.initialize(a_bzrdir, name)
2112
if (isinstance(a_bzrdir, RemoteBzrDir) and
2113
not isinstance(result, RemoteBranch)):
2114
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2118
def initialize(self, a_bzrdir, name=None, repository=None):
2119
# 1) get the network name to use.
2120
if self._custom_format:
2121
network_name = self._custom_format.network_name()
2123
# Select the current bzrlib default and ask for that.
2124
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2125
reference_format = reference_bzrdir_format.get_branch_format()
2126
self._custom_format = reference_format
2127
network_name = reference_format.network_name()
2128
# Being asked to create on a non RemoteBzrDir:
2129
if not isinstance(a_bzrdir, RemoteBzrDir):
2130
return self._vfs_initialize(a_bzrdir, name=name)
2131
medium = a_bzrdir._client._medium
2132
if medium._is_remote_before((1, 13)):
2133
return self._vfs_initialize(a_bzrdir, name=name)
2134
# Creating on a remote bzr dir.
2135
# 2) try direct creation via RPC
2136
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2137
if name is not None:
2138
# XXX JRV20100304: Support creating colocated branches
2139
raise errors.NoColocatedBranchSupport(self)
2140
verb = 'BzrDir.create_branch'
2142
response = a_bzrdir._call(verb, path, network_name)
2143
except errors.UnknownSmartMethod:
2144
# Fallback - use vfs methods
2145
medium._remember_remote_is_before((1, 13))
2146
return self._vfs_initialize(a_bzrdir, name=name)
2147
if response[0] != 'ok':
2148
raise errors.UnexpectedSmartServerResponse(response)
2149
# Turn the response into a RemoteRepository object.
2150
format = RemoteBranchFormat(network_name=response[1])
2151
repo_format = response_tuple_to_repo_format(response[3:])
2152
repo_path = response[2]
2153
if repository is not None:
2154
remote_repo_url = urlutils.join(medium.base, repo_path)
2155
url_diff = urlutils.relative_url(repository.user_url,
2158
raise AssertionError(
2159
'repository.user_url %r does not match URL from server '
2160
'response (%r + %r)'
2161
% (repository.user_url, medium.base, repo_path))
2162
remote_repo = repository
2165
repo_bzrdir = a_bzrdir
2167
repo_bzrdir = RemoteBzrDir(
2168
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2170
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2171
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2172
format=format, setup_stacking=False, name=name)
2173
# XXX: We know this is a new branch, so it must have revno 0, revid
2174
# NULL_REVISION. Creating the branch locked would make this be unable
2175
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2176
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2177
return remote_branch
2179
def make_tags(self, branch):
2181
return self._custom_format.make_tags(branch)
2183
def supports_tags(self):
2184
# Remote branches might support tags, but we won't know until we
2185
# access the real remote branch.
2187
return self._custom_format.supports_tags()
2189
def supports_stacking(self):
2191
return self._custom_format.supports_stacking()
2193
def supports_set_append_revisions_only(self):
2195
return self._custom_format.supports_set_append_revisions_only()
2198
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2199
"""Branch stored on a server accessed by HPSS RPC.
2201
At the moment most operations are mapped down to simple file operations.
2204
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2205
_client=None, format=None, setup_stacking=True, name=None):
2206
"""Create a RemoteBranch instance.
2208
:param real_branch: An optional local implementation of the branch
2209
format, usually accessing the data via the VFS.
2210
:param _client: Private parameter for testing.
2211
:param format: A RemoteBranchFormat object, None to create one
2212
automatically. If supplied it should have a network_name already
2214
:param setup_stacking: If True make an RPC call to determine the
2215
stacked (or not) status of the branch. If False assume the branch
2217
:param name: Colocated branch name
2219
# We intentionally don't call the parent class's __init__, because it
2220
# will try to assign to self.tags, which is a property in this subclass.
2221
# And the parent's __init__ doesn't do much anyway.
2222
self.bzrdir = remote_bzrdir
2223
if _client is not None:
2224
self._client = _client
2226
self._client = remote_bzrdir._client
2227
self.repository = remote_repository
2228
if real_branch is not None:
2229
self._real_branch = real_branch
2230
# Give the remote repository the matching real repo.
2231
real_repo = self._real_branch.repository
2232
if isinstance(real_repo, RemoteRepository):
2233
real_repo._ensure_real()
2234
real_repo = real_repo._real_repository
2235
self.repository._set_real_repository(real_repo)
2236
# Give the branch the remote repository to let fast-pathing happen.
2237
self._real_branch.repository = self.repository
2239
self._real_branch = None
2240
# Fill out expected attributes of branch for bzrlib API users.
2241
self._clear_cached_state()
2242
# TODO: deprecate self.base in favor of user_url
2243
self.base = self.bzrdir.user_url
2245
self._control_files = None
2246
self._lock_mode = None
2247
self._lock_token = None
2248
self._repo_lock_token = None
2249
self._lock_count = 0
2250
self._leave_lock = False
2251
# Setup a format: note that we cannot call _ensure_real until all the
2252
# attributes above are set: This code cannot be moved higher up in this
2255
self._format = RemoteBranchFormat()
2256
if real_branch is not None:
2257
self._format._network_name = \
2258
self._real_branch._format.network_name()
2260
self._format = format
2261
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2262
# branch.open_branch method.
2263
self._real_ignore_fallbacks = not setup_stacking
2264
if not self._format._network_name:
2265
# Did not get from open_branchV2 - old server.
2267
self._format._network_name = \
2268
self._real_branch._format.network_name()
2269
self.tags = self._format.make_tags(self)
2270
# The base class init is not called, so we duplicate this:
2271
hooks = branch.Branch.hooks['open']
2274
self._is_stacked = False
2276
self._setup_stacking()
2278
def _setup_stacking(self):
2279
# configure stacking into the remote repository, by reading it from
2282
fallback_url = self.get_stacked_on_url()
2283
except (errors.NotStacked, errors.UnstackableBranchFormat,
2284
errors.UnstackableRepositoryFormat), e:
2286
self._is_stacked = True
2287
self._activate_fallback_location(fallback_url)
2289
def _get_config(self):
2290
return RemoteBranchConfig(self)
2292
def _get_real_transport(self):
2293
# if we try vfs access, return the real branch's vfs transport
2295
return self._real_branch._transport
2297
_transport = property(_get_real_transport)
2300
return "%s(%s)" % (self.__class__.__name__, self.base)
2304
def _ensure_real(self):
2305
"""Ensure that there is a _real_branch set.
2307
Used before calls to self._real_branch.
2309
if self._real_branch is None:
2310
if not vfs.vfs_enabled():
2311
raise AssertionError('smart server vfs must be enabled '
2312
'to use vfs implementation')
2313
self.bzrdir._ensure_real()
2314
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2315
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2316
if self.repository._real_repository is None:
2317
# Give the remote repository the matching real repo.
2318
real_repo = self._real_branch.repository
2319
if isinstance(real_repo, RemoteRepository):
2320
real_repo._ensure_real()
2321
real_repo = real_repo._real_repository
2322
self.repository._set_real_repository(real_repo)
2323
# Give the real branch the remote repository to let fast-pathing
2325
self._real_branch.repository = self.repository
2326
if self._lock_mode == 'r':
2327
self._real_branch.lock_read()
2328
elif self._lock_mode == 'w':
2329
self._real_branch.lock_write(token=self._lock_token)
2331
def _translate_error(self, err, **context):
2332
self.repository._translate_error(err, branch=self, **context)
2334
def _clear_cached_state(self):
2335
super(RemoteBranch, self)._clear_cached_state()
2336
if self._real_branch is not None:
2337
self._real_branch._clear_cached_state()
2339
def _clear_cached_state_of_remote_branch_only(self):
2340
"""Like _clear_cached_state, but doesn't clear the cache of
2343
This is useful when falling back to calling a method of
2344
self._real_branch that changes state. In that case the underlying
2345
branch changes, so we need to invalidate this RemoteBranch's cache of
2346
it. However, there's no need to invalidate the _real_branch's cache
2347
too, in fact doing so might harm performance.
2349
super(RemoteBranch, self)._clear_cached_state()
2352
def control_files(self):
2353
# Defer actually creating RemoteBranchLockableFiles until its needed,
2354
# because it triggers an _ensure_real that we otherwise might not need.
2355
if self._control_files is None:
2356
self._control_files = RemoteBranchLockableFiles(
2357
self.bzrdir, self._client)
2358
return self._control_files
2360
def _get_checkout_format(self):
2362
return self._real_branch._get_checkout_format()
2364
def get_physical_lock_status(self):
2365
"""See Branch.get_physical_lock_status()."""
2366
# should be an API call to the server, as branches must be lockable.
2368
return self._real_branch.get_physical_lock_status()
2370
def get_stacked_on_url(self):
2371
"""Get the URL this branch is stacked against.
2373
:raises NotStacked: If the branch is not stacked.
2374
:raises UnstackableBranchFormat: If the branch does not support
2376
:raises UnstackableRepositoryFormat: If the repository does not support
2380
# there may not be a repository yet, so we can't use
2381
# self._translate_error, so we can't use self._call either.
2382
response = self._client.call('Branch.get_stacked_on_url',
2383
self._remote_path())
2384
except errors.ErrorFromSmartServer, err:
2385
# there may not be a repository yet, so we can't call through
2386
# its _translate_error
2387
_translate_error(err, branch=self)
2388
except errors.UnknownSmartMethod, err:
2390
return self._real_branch.get_stacked_on_url()
2391
if response[0] != 'ok':
2392
raise errors.UnexpectedSmartServerResponse(response)
2395
def set_stacked_on_url(self, url):
2396
branch.Branch.set_stacked_on_url(self, url)
2398
self._is_stacked = False
2400
self._is_stacked = True
2402
def _vfs_get_tags_bytes(self):
2404
return self._real_branch._get_tags_bytes()
2407
def _get_tags_bytes(self):
2408
if self._tags_bytes is None:
2409
self._tags_bytes = self._get_tags_bytes_via_hpss()
2410
return self._tags_bytes
2412
def _get_tags_bytes_via_hpss(self):
2413
medium = self._client._medium
2414
if medium._is_remote_before((1, 13)):
2415
return self._vfs_get_tags_bytes()
2417
response = self._call('Branch.get_tags_bytes', self._remote_path())
2418
except errors.UnknownSmartMethod:
2419
medium._remember_remote_is_before((1, 13))
2420
return self._vfs_get_tags_bytes()
2423
def _vfs_set_tags_bytes(self, bytes):
2425
return self._real_branch._set_tags_bytes(bytes)
2427
def _set_tags_bytes(self, bytes):
2428
if self.is_locked():
2429
self._tags_bytes = bytes
2430
medium = self._client._medium
2431
if medium._is_remote_before((1, 18)):
2432
self._vfs_set_tags_bytes(bytes)
2436
self._remote_path(), self._lock_token, self._repo_lock_token)
2437
response = self._call_with_body_bytes(
2438
'Branch.set_tags_bytes', args, bytes)
2439
except errors.UnknownSmartMethod:
2440
medium._remember_remote_is_before((1, 18))
2441
self._vfs_set_tags_bytes(bytes)
2443
def lock_read(self):
2444
"""Lock the branch for read operations.
2446
:return: A bzrlib.lock.LogicalLockResult.
2448
self.repository.lock_read()
2449
if not self._lock_mode:
2450
self._note_lock('r')
2451
self._lock_mode = 'r'
2452
self._lock_count = 1
2453
if self._real_branch is not None:
2454
self._real_branch.lock_read()
2456
self._lock_count += 1
2457
return lock.LogicalLockResult(self.unlock)
2459
def _remote_lock_write(self, token):
2461
branch_token = repo_token = ''
2463
branch_token = token
2464
repo_token = self.repository.lock_write().repository_token
2465
self.repository.unlock()
2466
err_context = {'token': token}
2468
response = self._call(
2469
'Branch.lock_write', self._remote_path(), branch_token,
2470
repo_token or '', **err_context)
2471
except errors.LockContention, e:
2472
# The LockContention from the server doesn't have any
2473
# information about the lock_url. We re-raise LockContention
2474
# with valid lock_url.
2475
raise errors.LockContention('(remote lock)',
2476
self.repository.base.split('.bzr/')[0])
2477
if response[0] != 'ok':
2478
raise errors.UnexpectedSmartServerResponse(response)
2479
ok, branch_token, repo_token = response
2480
return branch_token, repo_token
2482
def lock_write(self, token=None):
2483
if not self._lock_mode:
2484
self._note_lock('w')
2485
# Lock the branch and repo in one remote call.
2486
remote_tokens = self._remote_lock_write(token)
2487
self._lock_token, self._repo_lock_token = remote_tokens
2488
if not self._lock_token:
2489
raise SmartProtocolError('Remote server did not return a token!')
2490
# Tell the self.repository object that it is locked.
2491
self.repository.lock_write(
2492
self._repo_lock_token, _skip_rpc=True)
2494
if self._real_branch is not None:
2495
self._real_branch.lock_write(token=self._lock_token)
2496
if token is not None:
2497
self._leave_lock = True
2499
self._leave_lock = False
2500
self._lock_mode = 'w'
2501
self._lock_count = 1
2502
elif self._lock_mode == 'r':
2503
raise errors.ReadOnlyError(self)
2505
if token is not None:
2506
# A token was given to lock_write, and we're relocking, so
2507
# check that the given token actually matches the one we
2509
if token != self._lock_token:
2510
raise errors.TokenMismatch(token, self._lock_token)
2511
self._lock_count += 1
2512
# Re-lock the repository too.
2513
self.repository.lock_write(self._repo_lock_token)
2514
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2516
def _unlock(self, branch_token, repo_token):
2517
err_context = {'token': str((branch_token, repo_token))}
2518
response = self._call(
2519
'Branch.unlock', self._remote_path(), branch_token,
2520
repo_token or '', **err_context)
2521
if response == ('ok',):
2523
raise errors.UnexpectedSmartServerResponse(response)
2525
@only_raises(errors.LockNotHeld, errors.LockBroken)
2528
self._lock_count -= 1
2529
if not self._lock_count:
2530
self._clear_cached_state()
2531
mode = self._lock_mode
2532
self._lock_mode = None
2533
if self._real_branch is not None:
2534
if (not self._leave_lock and mode == 'w' and
2535
self._repo_lock_token):
2536
# If this RemoteBranch will remove the physical lock
2537
# for the repository, make sure the _real_branch
2538
# doesn't do it first. (Because the _real_branch's
2539
# repository is set to be the RemoteRepository.)
2540
self._real_branch.repository.leave_lock_in_place()
2541
self._real_branch.unlock()
2543
# Only write-locked branched need to make a remote method
2544
# call to perform the unlock.
2546
if not self._lock_token:
2547
raise AssertionError('Locked, but no token!')
2548
branch_token = self._lock_token
2549
repo_token = self._repo_lock_token
2550
self._lock_token = None
2551
self._repo_lock_token = None
2552
if not self._leave_lock:
2553
self._unlock(branch_token, repo_token)
2555
self.repository.unlock()
2557
def break_lock(self):
2559
return self._real_branch.break_lock()
2561
def leave_lock_in_place(self):
2562
if not self._lock_token:
2563
raise NotImplementedError(self.leave_lock_in_place)
2564
self._leave_lock = True
2566
def dont_leave_lock_in_place(self):
2567
if not self._lock_token:
2568
raise NotImplementedError(self.dont_leave_lock_in_place)
2569
self._leave_lock = False
2572
def get_rev_id(self, revno, history=None):
2574
return _mod_revision.NULL_REVISION
2575
last_revision_info = self.last_revision_info()
2576
ok, result = self.repository.get_rev_id_for_revno(
2577
revno, last_revision_info)
2580
missing_parent = result[1]
2581
# Either the revision named by the server is missing, or its parent
2582
# is. Call get_parent_map to determine which, so that we report a
2584
parent_map = self.repository.get_parent_map([missing_parent])
2585
if missing_parent in parent_map:
2586
missing_parent = parent_map[missing_parent]
2587
raise errors.RevisionNotPresent(missing_parent, self.repository)
2589
def _last_revision_info(self):
2590
response = self._call('Branch.last_revision_info', self._remote_path())
2591
if response[0] != 'ok':
2592
raise SmartProtocolError('unexpected response code %s' % (response,))
2593
revno = int(response[1])
2594
last_revision = response[2]
2595
return (revno, last_revision)
2597
def _gen_revision_history(self):
2598
"""See Branch._gen_revision_history()."""
2599
if self._is_stacked:
2601
return self._real_branch._gen_revision_history()
2602
response_tuple, response_handler = self._call_expecting_body(
2603
'Branch.revision_history', self._remote_path())
2604
if response_tuple[0] != 'ok':
2605
raise errors.UnexpectedSmartServerResponse(response_tuple)
2606
result = response_handler.read_body_bytes().split('\x00')
2611
def _remote_path(self):
2612
return self.bzrdir._path_for_remote_call(self._client)
2614
def _set_last_revision_descendant(self, revision_id, other_branch,
2615
allow_diverged=False, allow_overwrite_descendant=False):
2616
# This performs additional work to meet the hook contract; while its
2617
# undesirable, we have to synthesise the revno to call the hook, and
2618
# not calling the hook is worse as it means changes can't be prevented.
2619
# Having calculated this though, we can't just call into
2620
# set_last_revision_info as a simple call, because there is a set_rh
2621
# hook that some folk may still be using.
2622
old_revno, old_revid = self.last_revision_info()
2623
history = self._lefthand_history(revision_id)
2624
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2625
err_context = {'other_branch': other_branch}
2626
response = self._call('Branch.set_last_revision_ex',
2627
self._remote_path(), self._lock_token, self._repo_lock_token,
2628
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2630
self._clear_cached_state()
2631
if len(response) != 3 and response[0] != 'ok':
2632
raise errors.UnexpectedSmartServerResponse(response)
2633
new_revno, new_revision_id = response[1:]
2634
self._last_revision_info_cache = new_revno, new_revision_id
2635
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2636
if self._real_branch is not None:
2637
cache = new_revno, new_revision_id
2638
self._real_branch._last_revision_info_cache = cache
2640
def _set_last_revision(self, revision_id):
2641
old_revno, old_revid = self.last_revision_info()
2642
# This performs additional work to meet the hook contract; while its
2643
# undesirable, we have to synthesise the revno to call the hook, and
2644
# not calling the hook is worse as it means changes can't be prevented.
2645
# Having calculated this though, we can't just call into
2646
# set_last_revision_info as a simple call, because there is a set_rh
2647
# hook that some folk may still be using.
2648
history = self._lefthand_history(revision_id)
2649
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2650
self._clear_cached_state()
2651
response = self._call('Branch.set_last_revision',
2652
self._remote_path(), self._lock_token, self._repo_lock_token,
2654
if response != ('ok',):
2655
raise errors.UnexpectedSmartServerResponse(response)
2656
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2659
def set_revision_history(self, rev_history):
2660
# Send just the tip revision of the history; the server will generate
2661
# the full history from that. If the revision doesn't exist in this
2662
# branch, NoSuchRevision will be raised.
2663
if rev_history == []:
2666
rev_id = rev_history[-1]
2667
self._set_last_revision(rev_id)
2668
for hook in branch.Branch.hooks['set_rh']:
2669
hook(self, rev_history)
2670
self._cache_revision_history(rev_history)
2672
def _get_parent_location(self):
2673
medium = self._client._medium
2674
if medium._is_remote_before((1, 13)):
2675
return self._vfs_get_parent_location()
2677
response = self._call('Branch.get_parent', self._remote_path())
2678
except errors.UnknownSmartMethod:
2679
medium._remember_remote_is_before((1, 13))
2680
return self._vfs_get_parent_location()
2681
if len(response) != 1:
2682
raise errors.UnexpectedSmartServerResponse(response)
2683
parent_location = response[0]
2684
if parent_location == '':
2686
return parent_location
2688
def _vfs_get_parent_location(self):
2690
return self._real_branch._get_parent_location()
2692
def _set_parent_location(self, url):
2693
medium = self._client._medium
2694
if medium._is_remote_before((1, 15)):
2695
return self._vfs_set_parent_location(url)
2697
call_url = url or ''
2698
if type(call_url) is not str:
2699
raise AssertionError('url must be a str or None (%s)' % url)
2700
response = self._call('Branch.set_parent_location',
2701
self._remote_path(), self._lock_token, self._repo_lock_token,
2703
except errors.UnknownSmartMethod:
2704
medium._remember_remote_is_before((1, 15))
2705
return self._vfs_set_parent_location(url)
2707
raise errors.UnexpectedSmartServerResponse(response)
2709
def _vfs_set_parent_location(self, url):
2711
return self._real_branch._set_parent_location(url)
2714
def pull(self, source, overwrite=False, stop_revision=None,
2716
self._clear_cached_state_of_remote_branch_only()
2718
return self._real_branch.pull(
2719
source, overwrite=overwrite, stop_revision=stop_revision,
2720
_override_hook_target=self, **kwargs)
2723
def push(self, target, overwrite=False, stop_revision=None):
2725
return self._real_branch.push(
2726
target, overwrite=overwrite, stop_revision=stop_revision,
2727
_override_hook_source_branch=self)
2729
def is_locked(self):
2730
return self._lock_count >= 1
2733
def revision_id_to_revno(self, revision_id):
2735
return self._real_branch.revision_id_to_revno(revision_id)
2738
def set_last_revision_info(self, revno, revision_id):
2739
# XXX: These should be returned by the set_last_revision_info verb
2740
old_revno, old_revid = self.last_revision_info()
2741
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2742
revision_id = ensure_null(revision_id)
2744
response = self._call('Branch.set_last_revision_info',
2745
self._remote_path(), self._lock_token, self._repo_lock_token,
2746
str(revno), revision_id)
2747
except errors.UnknownSmartMethod:
2749
self._clear_cached_state_of_remote_branch_only()
2750
self._real_branch.set_last_revision_info(revno, revision_id)
2751
self._last_revision_info_cache = revno, revision_id
2753
if response == ('ok',):
2754
self._clear_cached_state()
2755
self._last_revision_info_cache = revno, revision_id
2756
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2757
# Update the _real_branch's cache too.
2758
if self._real_branch is not None:
2759
cache = self._last_revision_info_cache
2760
self._real_branch._last_revision_info_cache = cache
2762
raise errors.UnexpectedSmartServerResponse(response)
2765
def generate_revision_history(self, revision_id, last_rev=None,
2767
medium = self._client._medium
2768
if not medium._is_remote_before((1, 6)):
2769
# Use a smart method for 1.6 and above servers
2771
self._set_last_revision_descendant(revision_id, other_branch,
2772
allow_diverged=True, allow_overwrite_descendant=True)
2774
except errors.UnknownSmartMethod:
2775
medium._remember_remote_is_before((1, 6))
2776
self._clear_cached_state_of_remote_branch_only()
2777
self.set_revision_history(self._lefthand_history(revision_id,
2778
last_rev=last_rev,other_branch=other_branch))
2780
def set_push_location(self, location):
2782
return self._real_branch.set_push_location(location)
2785
class RemoteConfig(object):
2786
"""A Config that reads and writes from smart verbs.
2788
It is a low-level object that considers config data to be name/value pairs
2789
that may be associated with a section. Assigning meaning to the these
2790
values is done at higher levels like bzrlib.config.TreeConfig.
2793
def get_option(self, name, section=None, default=None):
2794
"""Return the value associated with a named option.
2796
:param name: The name of the value
2797
:param section: The section the option is in (if any)
2798
:param default: The value to return if the value is not set
2799
:return: The value or default value
2802
configobj = self._get_configobj()
2804
section_obj = configobj
2807
section_obj = configobj[section]
2810
return section_obj.get(name, default)
2811
except errors.UnknownSmartMethod:
2812
return self._vfs_get_option(name, section, default)
2814
def _response_to_configobj(self, response):
2815
if len(response[0]) and response[0][0] != 'ok':
2816
raise errors.UnexpectedSmartServerResponse(response)
2817
lines = response[1].read_body_bytes().splitlines()
2818
return config.ConfigObj(lines, encoding='utf-8')
2821
class RemoteBranchConfig(RemoteConfig):
2822
"""A RemoteConfig for Branches."""
2824
def __init__(self, branch):
2825
self._branch = branch
2827
def _get_configobj(self):
2828
path = self._branch._remote_path()
2829
response = self._branch._client.call_expecting_body(
2830
'Branch.get_config_file', path)
2831
return self._response_to_configobj(response)
2833
def set_option(self, value, name, section=None):
2834
"""Set the value associated with a named option.
2836
:param value: The value to set
2837
:param name: The name of the value to set
2838
:param section: The section the option is in (if any)
2840
medium = self._branch._client._medium
2841
if medium._is_remote_before((1, 14)):
2842
return self._vfs_set_option(value, name, section)
2843
if isinstance(value, dict):
2844
if medium._is_remote_before((2, 2)):
2845
return self._vfs_set_option(value, name, section)
2846
return self._set_config_option_dict(value, name, section)
2848
return self._set_config_option(value, name, section)
2850
def _set_config_option(self, value, name, section):
2852
path = self._branch._remote_path()
2853
response = self._branch._client.call('Branch.set_config_option',
2854
path, self._branch._lock_token, self._branch._repo_lock_token,
2855
value.encode('utf8'), name, section or '')
2856
except errors.UnknownSmartMethod:
2857
medium = self._branch._client._medium
2858
medium._remember_remote_is_before((1, 14))
2859
return self._vfs_set_option(value, name, section)
2861
raise errors.UnexpectedSmartServerResponse(response)
2863
def _serialize_option_dict(self, option_dict):
2865
for key, value in option_dict.items():
2866
if isinstance(key, unicode):
2867
key = key.encode('utf8')
2868
if isinstance(value, unicode):
2869
value = value.encode('utf8')
2870
utf8_dict[key] = value
2871
return bencode.bencode(utf8_dict)
2873
def _set_config_option_dict(self, value, name, section):
2875
path = self._branch._remote_path()
2876
serialised_dict = self._serialize_option_dict(value)
2877
response = self._branch._client.call(
2878
'Branch.set_config_option_dict',
2879
path, self._branch._lock_token, self._branch._repo_lock_token,
2880
serialised_dict, name, section or '')
2881
except errors.UnknownSmartMethod:
2882
medium = self._branch._client._medium
2883
medium._remember_remote_is_before((2, 2))
2884
return self._vfs_set_option(value, name, section)
2886
raise errors.UnexpectedSmartServerResponse(response)
2888
def _real_object(self):
2889
self._branch._ensure_real()
2890
return self._branch._real_branch
2892
def _vfs_set_option(self, value, name, section=None):
2893
return self._real_object()._get_config().set_option(
2894
value, name, section)
2897
class RemoteBzrDirConfig(RemoteConfig):
2898
"""A RemoteConfig for BzrDirs."""
2900
def __init__(self, bzrdir):
2901
self._bzrdir = bzrdir
2903
def _get_configobj(self):
2904
medium = self._bzrdir._client._medium
2905
verb = 'BzrDir.get_config_file'
2906
if medium._is_remote_before((1, 15)):
2907
raise errors.UnknownSmartMethod(verb)
2908
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2909
response = self._bzrdir._call_expecting_body(
2911
return self._response_to_configobj(response)
2913
def _vfs_get_option(self, name, section, default):
2914
return self._real_object()._get_config().get_option(
2915
name, section, default)
2917
def set_option(self, value, name, section=None):
2918
"""Set the value associated with a named option.
2920
:param value: The value to set
2921
:param name: The name of the value to set
2922
:param section: The section the option is in (if any)
2924
return self._real_object()._get_config().set_option(
2925
value, name, section)
2927
def _real_object(self):
2928
self._bzrdir._ensure_real()
2929
return self._bzrdir._real_bzrdir
2933
def _extract_tar(tar, to_dir):
2934
"""Extract all the contents of a tarfile object.
2936
A replacement for extractall, which is not present in python2.4
2939
tar.extract(tarinfo, to_dir)
2942
def _translate_error(err, **context):
2943
"""Translate an ErrorFromSmartServer into a more useful error.
2945
Possible context keys:
2953
If the error from the server doesn't match a known pattern, then
2954
UnknownErrorFromSmartServer is raised.
2958
return context[name]
2959
except KeyError, key_err:
2960
mutter('Missing key %r in context %r', key_err.args[0], context)
2963
"""Get the path from the context if present, otherwise use first error
2967
return context['path']
2968
except KeyError, key_err:
2970
return err.error_args[0]
2971
except IndexError, idx_err:
2973
'Missing key %r in context %r', key_err.args[0], context)
2976
if err.error_verb == 'IncompatibleRepositories':
2977
raise errors.IncompatibleRepositories(err.error_args[0],
2978
err.error_args[1], err.error_args[2])
2979
elif err.error_verb == 'NoSuchRevision':
2980
raise NoSuchRevision(find('branch'), err.error_args[0])
2981
elif err.error_verb == 'nosuchrevision':
2982
raise NoSuchRevision(find('repository'), err.error_args[0])
2983
elif err.error_verb == 'nobranch':
2984
if len(err.error_args) >= 1:
2985
extra = err.error_args[0]
2988
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2990
elif err.error_verb == 'norepository':
2991
raise errors.NoRepositoryPresent(find('bzrdir'))
2992
elif err.error_verb == 'LockContention':
2993
raise errors.LockContention('(remote lock)')
2994
elif err.error_verb == 'UnlockableTransport':
2995
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2996
elif err.error_verb == 'LockFailed':
2997
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2998
elif err.error_verb == 'TokenMismatch':
2999
raise errors.TokenMismatch(find('token'), '(remote token)')
3000
elif err.error_verb == 'Diverged':
3001
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3002
elif err.error_verb == 'TipChangeRejected':
3003
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3004
elif err.error_verb == 'UnstackableBranchFormat':
3005
raise errors.UnstackableBranchFormat(*err.error_args)
3006
elif err.error_verb == 'UnstackableRepositoryFormat':
3007
raise errors.UnstackableRepositoryFormat(*err.error_args)
3008
elif err.error_verb == 'NotStacked':
3009
raise errors.NotStacked(branch=find('branch'))
3010
elif err.error_verb == 'PermissionDenied':
3012
if len(err.error_args) >= 2:
3013
extra = err.error_args[1]
3016
raise errors.PermissionDenied(path, extra=extra)
3017
elif err.error_verb == 'ReadError':
3019
raise errors.ReadError(path)
3020
elif err.error_verb == 'NoSuchFile':
3022
raise errors.NoSuchFile(path)
3023
elif err.error_verb == 'FileExists':
3024
raise errors.FileExists(err.error_args[0])
3025
elif err.error_verb == 'DirectoryNotEmpty':
3026
raise errors.DirectoryNotEmpty(err.error_args[0])
3027
elif err.error_verb == 'ShortReadvError':
3028
args = err.error_args
3029
raise errors.ShortReadvError(
3030
args[0], int(args[1]), int(args[2]), int(args[3]))
3031
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3032
encoding = str(err.error_args[0]) # encoding must always be a string
3033
val = err.error_args[1]
3034
start = int(err.error_args[2])
3035
end = int(err.error_args[3])
3036
reason = str(err.error_args[4]) # reason must always be a string
3037
if val.startswith('u:'):
3038
val = val[2:].decode('utf-8')
3039
elif val.startswith('s:'):
3040
val = val[2:].decode('base64')
3041
if err.error_verb == 'UnicodeDecodeError':
3042
raise UnicodeDecodeError(encoding, val, start, end, reason)
3043
elif err.error_verb == 'UnicodeEncodeError':
3044
raise UnicodeEncodeError(encoding, val, start, end, reason)
3045
elif err.error_verb == 'ReadOnlyError':
3046
raise errors.TransportNotPossible('readonly transport')
3047
raise errors.UnknownErrorFromSmartServer(err)