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=None):
455
"""Upgrading of remote bzrdirs is not supported yet."""
457
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
458
% 'needs_format_conversion(format=None)')
461
def clone(self, url, revision_id=None, force_new_repo=False,
462
preserve_stacking=False):
464
return self._real_bzrdir.clone(url, revision_id=revision_id,
465
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
467
def _get_config(self):
468
return RemoteBzrDirConfig(self)
471
class RemoteRepositoryFormat(repository.RepositoryFormat):
472
"""Format for repositories accessed over a _SmartClient.
474
Instances of this repository are represented by RemoteRepository
477
The RemoteRepositoryFormat is parameterized during construction
478
to reflect the capabilities of the real, remote format. Specifically
479
the attributes rich_root_data and supports_tree_reference are set
480
on a per instance basis, and are not set (and should not be) at
483
:ivar _custom_format: If set, a specific concrete repository format that
484
will be used when initializing a repository with this
485
RemoteRepositoryFormat.
486
:ivar _creating_repo: If set, the repository object that this
487
RemoteRepositoryFormat was created for: it can be called into
488
to obtain data like the network name.
491
_matchingbzrdir = RemoteBzrDirFormat()
494
repository.RepositoryFormat.__init__(self)
495
self._custom_format = None
496
self._network_name = None
497
self._creating_bzrdir = None
498
self._supports_chks = None
499
self._supports_external_lookups = None
500
self._supports_tree_reference = None
501
self._rich_root_data = None
504
return "%s(_network_name=%r)" % (self.__class__.__name__,
508
def fast_deltas(self):
510
return self._custom_format.fast_deltas
513
def rich_root_data(self):
514
if self._rich_root_data is None:
516
self._rich_root_data = self._custom_format.rich_root_data
517
return self._rich_root_data
520
def supports_chks(self):
521
if self._supports_chks is None:
523
self._supports_chks = self._custom_format.supports_chks
524
return self._supports_chks
527
def supports_external_lookups(self):
528
if self._supports_external_lookups is None:
530
self._supports_external_lookups = \
531
self._custom_format.supports_external_lookups
532
return self._supports_external_lookups
535
def supports_tree_reference(self):
536
if self._supports_tree_reference is None:
538
self._supports_tree_reference = \
539
self._custom_format.supports_tree_reference
540
return self._supports_tree_reference
542
def _vfs_initialize(self, a_bzrdir, shared):
543
"""Helper for common code in initialize."""
544
if self._custom_format:
545
# Custom format requested
546
result = self._custom_format.initialize(a_bzrdir, shared=shared)
547
elif self._creating_bzrdir is not None:
548
# Use the format that the repository we were created to back
550
prior_repo = self._creating_bzrdir.open_repository()
551
prior_repo._ensure_real()
552
result = prior_repo._real_repository._format.initialize(
553
a_bzrdir, shared=shared)
555
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
556
# support remote initialization.
557
# We delegate to a real object at this point (as RemoteBzrDir
558
# delegate to the repository format which would lead to infinite
559
# recursion if we just called a_bzrdir.create_repository.
560
a_bzrdir._ensure_real()
561
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
562
if not isinstance(result, RemoteRepository):
563
return self.open(a_bzrdir)
567
def initialize(self, a_bzrdir, shared=False):
568
# Being asked to create on a non RemoteBzrDir:
569
if not isinstance(a_bzrdir, RemoteBzrDir):
570
return self._vfs_initialize(a_bzrdir, shared)
571
medium = a_bzrdir._client._medium
572
if medium._is_remote_before((1, 13)):
573
return self._vfs_initialize(a_bzrdir, shared)
574
# Creating on a remote bzr dir.
575
# 1) get the network name to use.
576
if self._custom_format:
577
network_name = self._custom_format.network_name()
578
elif self._network_name:
579
network_name = self._network_name
581
# Select the current bzrlib default and ask for that.
582
reference_bzrdir_format = bzrdir.format_registry.get('default')()
583
reference_format = reference_bzrdir_format.repository_format
584
network_name = reference_format.network_name()
585
# 2) try direct creation via RPC
586
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
587
verb = 'BzrDir.create_repository'
593
response = a_bzrdir._call(verb, path, network_name, shared_str)
594
except errors.UnknownSmartMethod:
595
# Fallback - use vfs methods
596
medium._remember_remote_is_before((1, 13))
597
return self._vfs_initialize(a_bzrdir, shared)
599
# Turn the response into a RemoteRepository object.
600
format = response_tuple_to_repo_format(response[1:])
601
# Used to support creating a real format instance when needed.
602
format._creating_bzrdir = a_bzrdir
603
remote_repo = RemoteRepository(a_bzrdir, format)
604
format._creating_repo = remote_repo
607
def open(self, a_bzrdir):
608
if not isinstance(a_bzrdir, RemoteBzrDir):
609
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
610
return a_bzrdir.open_repository()
612
def _ensure_real(self):
613
if self._custom_format is None:
614
self._custom_format = repository.network_format_registry.get(
618
def _fetch_order(self):
620
return self._custom_format._fetch_order
623
def _fetch_uses_deltas(self):
625
return self._custom_format._fetch_uses_deltas
628
def _fetch_reconcile(self):
630
return self._custom_format._fetch_reconcile
632
def get_format_description(self):
634
return 'Remote: ' + self._custom_format.get_format_description()
636
def __eq__(self, other):
637
return self.__class__ is other.__class__
639
def network_name(self):
640
if self._network_name:
641
return self._network_name
642
self._creating_repo._ensure_real()
643
return self._creating_repo._real_repository._format.network_name()
646
def pack_compresses(self):
648
return self._custom_format.pack_compresses
651
def _serializer(self):
653
return self._custom_format._serializer
656
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
657
controldir.ControlComponent):
658
"""Repository accessed over rpc.
660
For the moment most operations are performed using local transport-backed
664
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
665
"""Create a RemoteRepository instance.
667
:param remote_bzrdir: The bzrdir hosting this repository.
668
:param format: The RemoteFormat object to use.
669
:param real_repository: If not None, a local implementation of the
670
repository logic for the repository, usually accessing the data
672
:param _client: Private testing parameter - override the smart client
673
to be used by the repository.
676
self._real_repository = real_repository
678
self._real_repository = None
679
self.bzrdir = remote_bzrdir
681
self._client = remote_bzrdir._client
683
self._client = _client
684
self._format = format
685
self._lock_mode = None
686
self._lock_token = None
688
self._leave_lock = False
689
# Cache of revision parents; misses are cached during read locks, and
690
# write locks when no _real_repository has been set.
691
self._unstacked_provider = graph.CachingParentsProvider(
692
get_parent_map=self._get_parent_map_rpc)
693
self._unstacked_provider.disable_cache()
695
# These depend on the actual remote format, so force them off for
696
# maximum compatibility. XXX: In future these should depend on the
697
# remote repository instance, but this is irrelevant until we perform
698
# reconcile via an RPC call.
699
self._reconcile_does_inventory_gc = False
700
self._reconcile_fixes_text_parents = False
701
self._reconcile_backsup_inventory = False
702
self.base = self.bzrdir.transport.base
703
# Additional places to query for data.
704
self._fallback_repositories = []
707
def user_transport(self):
708
return self.bzrdir.user_transport
711
def control_transport(self):
712
# XXX: Normally you shouldn't directly get at the remote repository
713
# transport, but I'm not sure it's worth making this method
714
# optional -- mbp 2010-04-21
715
return self.bzrdir.get_repository_transport(None)
718
return "%s(%s)" % (self.__class__.__name__, self.base)
722
def abort_write_group(self, suppress_errors=False):
723
"""Complete a write group on the decorated repository.
725
Smart methods perform operations in a single step so this API
726
is not really applicable except as a compatibility thunk
727
for older plugins that don't use e.g. the CommitBuilder
730
:param suppress_errors: see Repository.abort_write_group.
733
return self._real_repository.abort_write_group(
734
suppress_errors=suppress_errors)
738
"""Decorate the real repository for now.
740
In the long term a full blown network facility is needed to avoid
741
creating a real repository object locally.
744
return self._real_repository.chk_bytes
746
def commit_write_group(self):
747
"""Complete a write group on the decorated repository.
749
Smart methods perform operations in a single step so this API
750
is not really applicable except as a compatibility thunk
751
for older plugins that don't use e.g. the CommitBuilder
755
return self._real_repository.commit_write_group()
757
def resume_write_group(self, tokens):
759
return self._real_repository.resume_write_group(tokens)
761
def suspend_write_group(self):
763
return self._real_repository.suspend_write_group()
765
def get_missing_parent_inventories(self, check_for_missing_texts=True):
767
return self._real_repository.get_missing_parent_inventories(
768
check_for_missing_texts=check_for_missing_texts)
770
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
772
return self._real_repository.get_rev_id_for_revno(
775
def get_rev_id_for_revno(self, revno, known_pair):
776
"""See Repository.get_rev_id_for_revno."""
777
path = self.bzrdir._path_for_remote_call(self._client)
779
if self._client._medium._is_remote_before((1, 17)):
780
return self._get_rev_id_for_revno_vfs(revno, known_pair)
781
response = self._call(
782
'Repository.get_rev_id_for_revno', path, revno, known_pair)
783
except errors.UnknownSmartMethod:
784
self._client._medium._remember_remote_is_before((1, 17))
785
return self._get_rev_id_for_revno_vfs(revno, known_pair)
786
if response[0] == 'ok':
787
return True, response[1]
788
elif response[0] == 'history-incomplete':
789
known_pair = response[1:3]
790
for fallback in self._fallback_repositories:
791
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
796
# Not found in any fallbacks
797
return False, known_pair
799
raise errors.UnexpectedSmartServerResponse(response)
801
def _ensure_real(self):
802
"""Ensure that there is a _real_repository set.
804
Used before calls to self._real_repository.
806
Note that _ensure_real causes many roundtrips to the server which are
807
not desirable, and prevents the use of smart one-roundtrip RPC's to
808
perform complex operations (such as accessing parent data, streaming
809
revisions etc). Adding calls to _ensure_real should only be done when
810
bringing up new functionality, adding fallbacks for smart methods that
811
require a fallback path, and never to replace an existing smart method
812
invocation. If in doubt chat to the bzr network team.
814
if self._real_repository is None:
815
if 'hpssvfs' in debug.debug_flags:
817
warning('VFS Repository access triggered\n%s',
818
''.join(traceback.format_stack()))
819
self._unstacked_provider.missing_keys.clear()
820
self.bzrdir._ensure_real()
821
self._set_real_repository(
822
self.bzrdir._real_bzrdir.open_repository())
824
def _translate_error(self, err, **context):
825
self.bzrdir._translate_error(err, repository=self, **context)
827
def find_text_key_references(self):
828
"""Find the text key references within the repository.
830
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
831
revision_ids. Each altered file-ids has the exact revision_ids that
832
altered it listed explicitly.
833
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
834
to whether they were referred to by the inventory of the
835
revision_id that they contain. The inventory texts from all present
836
revision ids are assessed to generate this report.
839
return self._real_repository.find_text_key_references()
841
def _generate_text_key_index(self):
842
"""Generate a new text key index for the repository.
844
This is an expensive function that will take considerable time to run.
846
:return: A dict mapping (file_id, revision_id) tuples to a list of
847
parents, also (file_id, revision_id) tuples.
850
return self._real_repository._generate_text_key_index()
852
def _get_revision_graph(self, revision_id):
853
"""Private method for using with old (< 1.2) servers to fallback."""
854
if revision_id is None:
856
elif revision.is_null(revision_id):
859
path = self.bzrdir._path_for_remote_call(self._client)
860
response = self._call_expecting_body(
861
'Repository.get_revision_graph', path, revision_id)
862
response_tuple, response_handler = response
863
if response_tuple[0] != 'ok':
864
raise errors.UnexpectedSmartServerResponse(response_tuple)
865
coded = response_handler.read_body_bytes()
867
# no revisions in this repository!
869
lines = coded.split('\n')
872
d = tuple(line.split())
873
revision_graph[d[0]] = d[1:]
875
return revision_graph
878
"""See Repository._get_sink()."""
879
return RemoteStreamSink(self)
881
def _get_source(self, to_format):
882
"""Return a source for streaming from this repository."""
883
return RemoteStreamSource(self, to_format)
886
def has_revision(self, revision_id):
887
"""True if this repository has a copy of the revision."""
888
# Copy of bzrlib.repository.Repository.has_revision
889
return revision_id in self.has_revisions((revision_id,))
892
def has_revisions(self, revision_ids):
893
"""Probe to find out the presence of multiple revisions.
895
:param revision_ids: An iterable of revision_ids.
896
:return: A set of the revision_ids that were present.
898
# Copy of bzrlib.repository.Repository.has_revisions
899
parent_map = self.get_parent_map(revision_ids)
900
result = set(parent_map)
901
if _mod_revision.NULL_REVISION in revision_ids:
902
result.add(_mod_revision.NULL_REVISION)
905
def _has_same_fallbacks(self, other_repo):
906
"""Returns true if the repositories have the same fallbacks."""
907
# XXX: copied from Repository; it should be unified into a base class
908
# <https://bugs.launchpad.net/bzr/+bug/401622>
909
my_fb = self._fallback_repositories
910
other_fb = other_repo._fallback_repositories
911
if len(my_fb) != len(other_fb):
913
for f, g in zip(my_fb, other_fb):
914
if not f.has_same_location(g):
918
def has_same_location(self, other):
919
# TODO: Move to RepositoryBase and unify with the regular Repository
920
# one; unfortunately the tests rely on slightly different behaviour at
921
# present -- mbp 20090710
922
return (self.__class__ is other.__class__ and
923
self.bzrdir.transport.base == other.bzrdir.transport.base)
925
def get_graph(self, other_repository=None):
926
"""Return the graph for this repository format"""
927
parents_provider = self._make_parents_provider(other_repository)
928
return graph.Graph(parents_provider)
931
def get_known_graph_ancestry(self, revision_ids):
932
"""Return the known graph for a set of revision ids and their ancestors.
934
st = static_tuple.StaticTuple
935
revision_keys = [st(r_id).intern() for r_id in revision_ids]
936
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
937
return graph.GraphThunkIdsToKeys(known_graph)
939
def gather_stats(self, revid=None, committers=None):
940
"""See Repository.gather_stats()."""
941
path = self.bzrdir._path_for_remote_call(self._client)
942
# revid can be None to indicate no revisions, not just NULL_REVISION
943
if revid is None or revision.is_null(revid):
947
if committers is None or not committers:
948
fmt_committers = 'no'
950
fmt_committers = 'yes'
951
response_tuple, response_handler = self._call_expecting_body(
952
'Repository.gather_stats', path, fmt_revid, fmt_committers)
953
if response_tuple[0] != 'ok':
954
raise errors.UnexpectedSmartServerResponse(response_tuple)
956
body = response_handler.read_body_bytes()
958
for line in body.split('\n'):
961
key, val_text = line.split(':')
962
if key in ('revisions', 'size', 'committers'):
963
result[key] = int(val_text)
964
elif key in ('firstrev', 'latestrev'):
965
values = val_text.split(' ')[1:]
966
result[key] = (float(values[0]), long(values[1]))
970
def find_branches(self, using=False):
971
"""See Repository.find_branches()."""
972
# should be an API call to the server.
974
return self._real_repository.find_branches(using=using)
976
def get_physical_lock_status(self):
977
"""See Repository.get_physical_lock_status()."""
978
# should be an API call to the server.
980
return self._real_repository.get_physical_lock_status()
982
def is_in_write_group(self):
983
"""Return True if there is an open write group.
985
write groups are only applicable locally for the smart server..
987
if self._real_repository:
988
return self._real_repository.is_in_write_group()
991
return self._lock_count >= 1
994
"""See Repository.is_shared()."""
995
path = self.bzrdir._path_for_remote_call(self._client)
996
response = self._call('Repository.is_shared', path)
997
if response[0] not in ('yes', 'no'):
998
raise SmartProtocolError('unexpected response code %s' % (response,))
999
return response[0] == 'yes'
1001
def is_write_locked(self):
1002
return self._lock_mode == 'w'
1004
def _warn_if_deprecated(self, branch=None):
1005
# If we have a real repository, the check will be done there, if we
1006
# don't the check will be done remotely.
1009
def lock_read(self):
1010
"""Lock the repository for read operations.
1012
:return: A bzrlib.lock.LogicalLockResult.
1014
# wrong eventually - want a local lock cache context
1015
if not self._lock_mode:
1016
self._note_lock('r')
1017
self._lock_mode = 'r'
1018
self._lock_count = 1
1019
self._unstacked_provider.enable_cache(cache_misses=True)
1020
if self._real_repository is not None:
1021
self._real_repository.lock_read()
1022
for repo in self._fallback_repositories:
1025
self._lock_count += 1
1026
return lock.LogicalLockResult(self.unlock)
1028
def _remote_lock_write(self, token):
1029
path = self.bzrdir._path_for_remote_call(self._client)
1032
err_context = {'token': token}
1033
response = self._call('Repository.lock_write', path, token,
1035
if response[0] == 'ok':
1036
ok, token = response
1039
raise errors.UnexpectedSmartServerResponse(response)
1041
def lock_write(self, token=None, _skip_rpc=False):
1042
if not self._lock_mode:
1043
self._note_lock('w')
1045
if self._lock_token is not None:
1046
if token != self._lock_token:
1047
raise errors.TokenMismatch(token, self._lock_token)
1048
self._lock_token = token
1050
self._lock_token = self._remote_lock_write(token)
1051
# if self._lock_token is None, then this is something like packs or
1052
# svn where we don't get to lock the repo, or a weave style repository
1053
# where we cannot lock it over the wire and attempts to do so will
1055
if self._real_repository is not None:
1056
self._real_repository.lock_write(token=self._lock_token)
1057
if token is not None:
1058
self._leave_lock = True
1060
self._leave_lock = False
1061
self._lock_mode = 'w'
1062
self._lock_count = 1
1063
cache_misses = self._real_repository is None
1064
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1065
for repo in self._fallback_repositories:
1066
# Writes don't affect fallback repos
1068
elif self._lock_mode == 'r':
1069
raise errors.ReadOnlyError(self)
1071
self._lock_count += 1
1072
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1074
def leave_lock_in_place(self):
1075
if not self._lock_token:
1076
raise NotImplementedError(self.leave_lock_in_place)
1077
self._leave_lock = True
1079
def dont_leave_lock_in_place(self):
1080
if not self._lock_token:
1081
raise NotImplementedError(self.dont_leave_lock_in_place)
1082
self._leave_lock = False
1084
def _set_real_repository(self, repository):
1085
"""Set the _real_repository for this repository.
1087
:param repository: The repository to fallback to for non-hpss
1088
implemented operations.
1090
if self._real_repository is not None:
1091
# Replacing an already set real repository.
1092
# We cannot do this [currently] if the repository is locked -
1093
# synchronised state might be lost.
1094
if self.is_locked():
1095
raise AssertionError('_real_repository is already set')
1096
if isinstance(repository, RemoteRepository):
1097
raise AssertionError()
1098
self._real_repository = repository
1099
# three code paths happen here:
1100
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1101
# up stacking. In this case self._fallback_repositories is [], and the
1102
# real repo is already setup. Preserve the real repo and
1103
# RemoteRepository.add_fallback_repository will avoid adding
1105
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1106
# ensure_real is triggered from a branch, the real repository to
1107
# set already has a matching list with separate instances, but
1108
# as they are also RemoteRepositories we don't worry about making the
1109
# lists be identical.
1110
# 3) new servers, RemoteRepository.ensure_real is triggered before
1111
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1112
# and need to populate it.
1113
if (self._fallback_repositories and
1114
len(self._real_repository._fallback_repositories) !=
1115
len(self._fallback_repositories)):
1116
if len(self._real_repository._fallback_repositories):
1117
raise AssertionError(
1118
"cannot cleanly remove existing _fallback_repositories")
1119
for fb in self._fallback_repositories:
1120
self._real_repository.add_fallback_repository(fb)
1121
if self._lock_mode == 'w':
1122
# if we are already locked, the real repository must be able to
1123
# acquire the lock with our token.
1124
self._real_repository.lock_write(self._lock_token)
1125
elif self._lock_mode == 'r':
1126
self._real_repository.lock_read()
1128
def start_write_group(self):
1129
"""Start a write group on the decorated repository.
1131
Smart methods perform operations in a single step so this API
1132
is not really applicable except as a compatibility thunk
1133
for older plugins that don't use e.g. the CommitBuilder
1137
return self._real_repository.start_write_group()
1139
def _unlock(self, token):
1140
path = self.bzrdir._path_for_remote_call(self._client)
1142
# with no token the remote repository is not persistently locked.
1144
err_context = {'token': token}
1145
response = self._call('Repository.unlock', path, token,
1147
if response == ('ok',):
1150
raise errors.UnexpectedSmartServerResponse(response)
1152
@only_raises(errors.LockNotHeld, errors.LockBroken)
1154
if not self._lock_count:
1155
return lock.cant_unlock_not_held(self)
1156
self._lock_count -= 1
1157
if self._lock_count > 0:
1159
self._unstacked_provider.disable_cache()
1160
old_mode = self._lock_mode
1161
self._lock_mode = None
1163
# The real repository is responsible at present for raising an
1164
# exception if it's in an unfinished write group. However, it
1165
# normally will *not* actually remove the lock from disk - that's
1166
# done by the server on receiving the Repository.unlock call.
1167
# This is just to let the _real_repository stay up to date.
1168
if self._real_repository is not None:
1169
self._real_repository.unlock()
1171
# The rpc-level lock should be released even if there was a
1172
# problem releasing the vfs-based lock.
1174
# Only write-locked repositories need to make a remote method
1175
# call to perform the unlock.
1176
old_token = self._lock_token
1177
self._lock_token = None
1178
if not self._leave_lock:
1179
self._unlock(old_token)
1180
# Fallbacks are always 'lock_read()' so we don't pay attention to
1182
for repo in self._fallback_repositories:
1185
def break_lock(self):
1186
# should hand off to the network
1188
return self._real_repository.break_lock()
1190
def _get_tarball(self, compression):
1191
"""Return a TemporaryFile containing a repository tarball.
1193
Returns None if the server does not support sending tarballs.
1196
path = self.bzrdir._path_for_remote_call(self._client)
1198
response, protocol = self._call_expecting_body(
1199
'Repository.tarball', path, compression)
1200
except errors.UnknownSmartMethod:
1201
protocol.cancel_read_body()
1203
if response[0] == 'ok':
1204
# Extract the tarball and return it
1205
t = tempfile.NamedTemporaryFile()
1206
# TODO: rpc layer should read directly into it...
1207
t.write(protocol.read_body_bytes())
1210
raise errors.UnexpectedSmartServerResponse(response)
1212
def sprout(self, to_bzrdir, revision_id=None):
1213
# TODO: Option to control what format is created?
1215
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1217
dest_repo.fetch(self, revision_id=revision_id)
1220
### These methods are just thin shims to the VFS object for now.
1222
def revision_tree(self, revision_id):
1224
return self._real_repository.revision_tree(revision_id)
1226
def get_serializer_format(self):
1228
return self._real_repository.get_serializer_format()
1230
def get_commit_builder(self, branch, parents, config, timestamp=None,
1231
timezone=None, committer=None, revprops=None,
1233
# FIXME: It ought to be possible to call this without immediately
1234
# triggering _ensure_real. For now it's the easiest thing to do.
1236
real_repo = self._real_repository
1237
builder = real_repo.get_commit_builder(branch, parents,
1238
config, timestamp=timestamp, timezone=timezone,
1239
committer=committer, revprops=revprops, revision_id=revision_id)
1242
def add_fallback_repository(self, repository):
1243
"""Add a repository to use for looking up data not held locally.
1245
:param repository: A repository.
1247
if not self._format.supports_external_lookups:
1248
raise errors.UnstackableRepositoryFormat(
1249
self._format.network_name(), self.base)
1250
# We need to accumulate additional repositories here, to pass them in
1253
if self.is_locked():
1254
# We will call fallback.unlock() when we transition to the unlocked
1255
# state, so always add a lock here. If a caller passes us a locked
1256
# repository, they are responsible for unlocking it later.
1257
repository.lock_read()
1258
self._check_fallback_repository(repository)
1259
self._fallback_repositories.append(repository)
1260
# If self._real_repository was parameterised already (e.g. because a
1261
# _real_branch had its get_stacked_on_url method called), then the
1262
# repository to be added may already be in the _real_repositories list.
1263
if self._real_repository is not None:
1264
fallback_locations = [repo.user_url for repo in
1265
self._real_repository._fallback_repositories]
1266
if repository.user_url not in fallback_locations:
1267
self._real_repository.add_fallback_repository(repository)
1269
def _check_fallback_repository(self, repository):
1270
"""Check that this repository can fallback to repository safely.
1272
Raise an error if not.
1274
:param repository: A repository to fallback to.
1276
return _mod_repository.InterRepository._assert_same_model(
1279
def add_inventory(self, revid, inv, parents):
1281
return self._real_repository.add_inventory(revid, inv, parents)
1283
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1284
parents, basis_inv=None, propagate_caches=False):
1286
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1287
delta, new_revision_id, parents, basis_inv=basis_inv,
1288
propagate_caches=propagate_caches)
1290
def add_revision(self, rev_id, rev, inv=None, config=None):
1292
return self._real_repository.add_revision(
1293
rev_id, rev, inv=inv, config=config)
1296
def get_inventory(self, revision_id):
1298
return self._real_repository.get_inventory(revision_id)
1300
def iter_inventories(self, revision_ids, ordering=None):
1302
return self._real_repository.iter_inventories(revision_ids, ordering)
1305
def get_revision(self, revision_id):
1307
return self._real_repository.get_revision(revision_id)
1309
def get_transaction(self):
1311
return self._real_repository.get_transaction()
1314
def clone(self, a_bzrdir, revision_id=None):
1316
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1318
def make_working_trees(self):
1319
"""See Repository.make_working_trees"""
1321
return self._real_repository.make_working_trees()
1323
def refresh_data(self):
1324
"""Re-read any data needed to synchronise with disk.
1326
This method is intended to be called after another repository instance
1327
(such as one used by a smart server) has inserted data into the
1328
repository. On all repositories this will work outside of write groups.
1329
Some repository formats (pack and newer for bzrlib native formats)
1330
support refresh_data inside write groups. If called inside a write
1331
group on a repository that does not support refreshing in a write group
1332
IsInWriteGroupError will be raised.
1334
if self._real_repository is not None:
1335
self._real_repository.refresh_data()
1337
def revision_ids_to_search_result(self, result_set):
1338
"""Convert a set of revision ids to a graph SearchResult."""
1339
result_parents = set()
1340
for parents in self.get_graph().get_parent_map(
1341
result_set).itervalues():
1342
result_parents.update(parents)
1343
included_keys = result_set.intersection(result_parents)
1344
start_keys = result_set.difference(included_keys)
1345
exclude_keys = result_parents.difference(result_set)
1346
result = graph.SearchResult(start_keys, exclude_keys,
1347
len(result_set), result_set)
1351
def search_missing_revision_ids(self, other,
1352
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1353
find_ghosts=True, revision_ids=None, if_present_ids=None):
1354
"""Return the revision ids that other has that this does not.
1356
These are returned in topological order.
1358
revision_id: only return revision ids included by revision_id.
1360
if symbol_versioning.deprecated_passed(revision_id):
1361
symbol_versioning.warn(
1362
'search_missing_revision_ids(revision_id=...) was '
1363
'deprecated in 2.4. Use revision_ids=[...] instead.',
1364
DeprecationWarning, stacklevel=2)
1365
if revision_ids is not None:
1366
raise AssertionError(
1367
'revision_ids is mutually exclusive with revision_id')
1368
if revision_id is not None:
1369
revision_ids = [revision_id]
1370
inter_repo = repository.InterRepository.get(other, self)
1371
return inter_repo.search_missing_revision_ids(
1372
find_ghosts=find_ghosts, revision_ids=revision_ids,
1373
if_present_ids=if_present_ids)
1375
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1377
# No base implementation to use as RemoteRepository is not a subclass
1378
# of Repository; so this is a copy of Repository.fetch().
1379
if fetch_spec is not None and revision_id is not None:
1380
raise AssertionError(
1381
"fetch_spec and revision_id are mutually exclusive.")
1382
if self.is_in_write_group():
1383
raise errors.InternalBzrError(
1384
"May not fetch while in a write group.")
1385
# fast path same-url fetch operations
1386
if (self.has_same_location(source)
1387
and fetch_spec is None
1388
and self._has_same_fallbacks(source)):
1389
# check that last_revision is in 'from' and then return a
1391
if (revision_id is not None and
1392
not revision.is_null(revision_id)):
1393
self.get_revision(revision_id)
1395
# if there is no specific appropriate InterRepository, this will get
1396
# the InterRepository base class, which raises an
1397
# IncompatibleRepositories when asked to fetch.
1398
inter = repository.InterRepository.get(source, self)
1399
return inter.fetch(revision_id=revision_id, pb=pb,
1400
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1402
def create_bundle(self, target, base, fileobj, format=None):
1404
self._real_repository.create_bundle(target, base, fileobj, format)
1407
def get_ancestry(self, revision_id, topo_sorted=True):
1409
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1411
def fileids_altered_by_revision_ids(self, revision_ids):
1413
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1415
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1417
return self._real_repository._get_versioned_file_checker(
1418
revisions, revision_versions_cache)
1420
def iter_files_bytes(self, desired_files):
1421
"""See Repository.iter_file_bytes.
1424
return self._real_repository.iter_files_bytes(desired_files)
1426
def get_parent_map(self, revision_ids):
1427
"""See bzrlib.Graph.get_parent_map()."""
1428
return self._make_parents_provider().get_parent_map(revision_ids)
1430
def _get_parent_map_rpc(self, keys):
1431
"""Helper for get_parent_map that performs the RPC."""
1432
medium = self._client._medium
1433
if medium._is_remote_before((1, 2)):
1434
# We already found out that the server can't understand
1435
# Repository.get_parent_map requests, so just fetch the whole
1438
# Note that this reads the whole graph, when only some keys are
1439
# wanted. On this old server there's no way (?) to get them all
1440
# in one go, and the user probably will have seen a warning about
1441
# the server being old anyhow.
1442
rg = self._get_revision_graph(None)
1443
# There is an API discrepancy between get_parent_map and
1444
# get_revision_graph. Specifically, a "key:()" pair in
1445
# get_revision_graph just means a node has no parents. For
1446
# "get_parent_map" it means the node is a ghost. So fix up the
1447
# graph to correct this.
1448
# https://bugs.launchpad.net/bzr/+bug/214894
1449
# There is one other "bug" which is that ghosts in
1450
# get_revision_graph() are not returned at all. But we won't worry
1451
# about that for now.
1452
for node_id, parent_ids in rg.iteritems():
1453
if parent_ids == ():
1454
rg[node_id] = (NULL_REVISION,)
1455
rg[NULL_REVISION] = ()
1460
raise ValueError('get_parent_map(None) is not valid')
1461
if NULL_REVISION in keys:
1462
keys.discard(NULL_REVISION)
1463
found_parents = {NULL_REVISION:()}
1465
return found_parents
1468
# TODO(Needs analysis): We could assume that the keys being requested
1469
# from get_parent_map are in a breadth first search, so typically they
1470
# will all be depth N from some common parent, and we don't have to
1471
# have the server iterate from the root parent, but rather from the
1472
# keys we're searching; and just tell the server the keyspace we
1473
# already have; but this may be more traffic again.
1475
# Transform self._parents_map into a search request recipe.
1476
# TODO: Manage this incrementally to avoid covering the same path
1477
# repeatedly. (The server will have to on each request, but the less
1478
# work done the better).
1480
# Negative caching notes:
1481
# new server sends missing when a request including the revid
1482
# 'include-missing:' is present in the request.
1483
# missing keys are serialised as missing:X, and we then call
1484
# provider.note_missing(X) for-all X
1485
parents_map = self._unstacked_provider.get_cached_map()
1486
if parents_map is None:
1487
# Repository is not locked, so there's no cache.
1489
# start_set is all the keys in the cache
1490
start_set = set(parents_map)
1491
# result set is all the references to keys in the cache
1492
result_parents = set()
1493
for parents in parents_map.itervalues():
1494
result_parents.update(parents)
1495
stop_keys = result_parents.difference(start_set)
1496
# We don't need to send ghosts back to the server as a position to
1498
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1499
key_count = len(parents_map)
1500
if (NULL_REVISION in result_parents
1501
and NULL_REVISION in self._unstacked_provider.missing_keys):
1502
# If we pruned NULL_REVISION from the stop_keys because it's also
1503
# in our cache of "missing" keys we need to increment our key count
1504
# by 1, because the reconsitituted SearchResult on the server will
1505
# still consider NULL_REVISION to be an included key.
1507
included_keys = start_set.intersection(result_parents)
1508
start_set.difference_update(included_keys)
1509
recipe = ('manual', start_set, stop_keys, key_count)
1510
body = self._serialise_search_recipe(recipe)
1511
path = self.bzrdir._path_for_remote_call(self._client)
1513
if type(key) is not str:
1515
"key %r not a plain string" % (key,))
1516
verb = 'Repository.get_parent_map'
1517
args = (path, 'include-missing:') + tuple(keys)
1519
response = self._call_with_body_bytes_expecting_body(
1521
except errors.UnknownSmartMethod:
1522
# Server does not support this method, so get the whole graph.
1523
# Worse, we have to force a disconnection, because the server now
1524
# doesn't realise it has a body on the wire to consume, so the
1525
# only way to recover is to abandon the connection.
1527
'Server is too old for fast get_parent_map, reconnecting. '
1528
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1530
# To avoid having to disconnect repeatedly, we keep track of the
1531
# fact the server doesn't understand remote methods added in 1.2.
1532
medium._remember_remote_is_before((1, 2))
1533
# Recurse just once and we should use the fallback code.
1534
return self._get_parent_map_rpc(keys)
1535
response_tuple, response_handler = response
1536
if response_tuple[0] not in ['ok']:
1537
response_handler.cancel_read_body()
1538
raise errors.UnexpectedSmartServerResponse(response_tuple)
1539
if response_tuple[0] == 'ok':
1540
coded = bz2.decompress(response_handler.read_body_bytes())
1542
# no revisions found
1544
lines = coded.split('\n')
1547
d = tuple(line.split())
1549
revision_graph[d[0]] = d[1:]
1552
if d[0].startswith('missing:'):
1554
self._unstacked_provider.note_missing_key(revid)
1556
# no parents - so give the Graph result
1558
revision_graph[d[0]] = (NULL_REVISION,)
1559
return revision_graph
1562
def get_signature_text(self, revision_id):
1564
return self._real_repository.get_signature_text(revision_id)
1567
def _get_inventory_xml(self, revision_id):
1569
return self._real_repository._get_inventory_xml(revision_id)
1571
def reconcile(self, other=None, thorough=False):
1573
return self._real_repository.reconcile(other=other, thorough=thorough)
1575
def all_revision_ids(self):
1577
return self._real_repository.all_revision_ids()
1580
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1582
return self._real_repository.get_deltas_for_revisions(revisions,
1583
specific_fileids=specific_fileids)
1586
def get_revision_delta(self, revision_id, specific_fileids=None):
1588
return self._real_repository.get_revision_delta(revision_id,
1589
specific_fileids=specific_fileids)
1592
def revision_trees(self, revision_ids):
1594
return self._real_repository.revision_trees(revision_ids)
1597
def get_revision_reconcile(self, revision_id):
1599
return self._real_repository.get_revision_reconcile(revision_id)
1602
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1604
return self._real_repository.check(revision_ids=revision_ids,
1605
callback_refs=callback_refs, check_repo=check_repo)
1607
def copy_content_into(self, destination, revision_id=None):
1609
return self._real_repository.copy_content_into(
1610
destination, revision_id=revision_id)
1612
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1613
# get a tarball of the remote repository, and copy from that into the
1615
from bzrlib import osutils
1617
# TODO: Maybe a progress bar while streaming the tarball?
1618
note("Copying repository content as tarball...")
1619
tar_file = self._get_tarball('bz2')
1620
if tar_file is None:
1622
destination = to_bzrdir.create_repository()
1624
tar = tarfile.open('repository', fileobj=tar_file,
1626
tmpdir = osutils.mkdtemp()
1628
_extract_tar(tar, tmpdir)
1629
tmp_bzrdir = BzrDir.open(tmpdir)
1630
tmp_repo = tmp_bzrdir.open_repository()
1631
tmp_repo.copy_content_into(destination, revision_id)
1633
osutils.rmtree(tmpdir)
1637
# TODO: Suggestion from john: using external tar is much faster than
1638
# python's tarfile library, but it may not work on windows.
1641
def inventories(self):
1642
"""Decorate the real repository for now.
1644
In the long term a full blown network facility is needed to
1645
avoid creating a real repository object locally.
1648
return self._real_repository.inventories
1651
def pack(self, hint=None, clean_obsolete_packs=False):
1652
"""Compress the data within the repository.
1654
This is not currently implemented within the smart server.
1657
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1660
def revisions(self):
1661
"""Decorate the real repository for now.
1663
In the short term this should become a real object to intercept graph
1666
In the long term a full blown network facility is needed.
1669
return self._real_repository.revisions
1671
def set_make_working_trees(self, new_value):
1673
new_value_str = "True"
1675
new_value_str = "False"
1676
path = self.bzrdir._path_for_remote_call(self._client)
1678
response = self._call(
1679
'Repository.set_make_working_trees', path, new_value_str)
1680
except errors.UnknownSmartMethod:
1682
self._real_repository.set_make_working_trees(new_value)
1684
if response[0] != 'ok':
1685
raise errors.UnexpectedSmartServerResponse(response)
1688
def signatures(self):
1689
"""Decorate the real repository for now.
1691
In the long term a full blown network facility is needed to avoid
1692
creating a real repository object locally.
1695
return self._real_repository.signatures
1698
def sign_revision(self, revision_id, gpg_strategy):
1700
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1704
"""Decorate the real repository for now.
1706
In the long term a full blown network facility is needed to avoid
1707
creating a real repository object locally.
1710
return self._real_repository.texts
1713
def get_revisions(self, revision_ids):
1715
return self._real_repository.get_revisions(revision_ids)
1717
def supports_rich_root(self):
1718
return self._format.rich_root_data
1720
def iter_reverse_revision_history(self, revision_id):
1722
return self._real_repository.iter_reverse_revision_history(revision_id)
1725
def _serializer(self):
1726
return self._format._serializer
1728
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1730
return self._real_repository.store_revision_signature(
1731
gpg_strategy, plaintext, revision_id)
1733
def add_signature_text(self, revision_id, signature):
1735
return self._real_repository.add_signature_text(revision_id, signature)
1737
def has_signature_for_revision_id(self, revision_id):
1739
return self._real_repository.has_signature_for_revision_id(revision_id)
1741
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1743
return self._real_repository.item_keys_introduced_by(revision_ids,
1744
_files_pb=_files_pb)
1746
def revision_graph_can_have_wrong_parents(self):
1747
# The answer depends on the remote repo format.
1749
return self._real_repository.revision_graph_can_have_wrong_parents()
1751
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1753
return self._real_repository._find_inconsistent_revision_parents(
1756
def _check_for_inconsistent_revision_parents(self):
1758
return self._real_repository._check_for_inconsistent_revision_parents()
1760
def _make_parents_provider(self, other=None):
1761
providers = [self._unstacked_provider]
1762
if other is not None:
1763
providers.insert(0, other)
1764
providers.extend(r._make_parents_provider() for r in
1765
self._fallback_repositories)
1766
return graph.StackedParentsProvider(providers)
1768
def _serialise_search_recipe(self, recipe):
1769
"""Serialise a graph search recipe.
1771
:param recipe: A search recipe (start, stop, count).
1772
:return: Serialised bytes.
1774
start_keys = ' '.join(recipe[1])
1775
stop_keys = ' '.join(recipe[2])
1776
count = str(recipe[3])
1777
return '\n'.join((start_keys, stop_keys, count))
1779
def _serialise_search_result(self, search_result):
1780
parts = search_result.get_network_struct()
1781
return '\n'.join(parts)
1784
path = self.bzrdir._path_for_remote_call(self._client)
1786
response = self._call('PackRepository.autopack', path)
1787
except errors.UnknownSmartMethod:
1789
self._real_repository._pack_collection.autopack()
1792
if response[0] != 'ok':
1793
raise errors.UnexpectedSmartServerResponse(response)
1796
class RemoteStreamSink(repository.StreamSink):
1798
def _insert_real(self, stream, src_format, resume_tokens):
1799
self.target_repo._ensure_real()
1800
sink = self.target_repo._real_repository._get_sink()
1801
result = sink.insert_stream(stream, src_format, resume_tokens)
1803
self.target_repo.autopack()
1806
def insert_stream(self, stream, src_format, resume_tokens):
1807
target = self.target_repo
1808
target._unstacked_provider.missing_keys.clear()
1809
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1810
if target._lock_token:
1811
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1812
lock_args = (target._lock_token or '',)
1814
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1816
client = target._client
1817
medium = client._medium
1818
path = target.bzrdir._path_for_remote_call(client)
1819
# Probe for the verb to use with an empty stream before sending the
1820
# real stream to it. We do this both to avoid the risk of sending a
1821
# large request that is then rejected, and because we don't want to
1822
# implement a way to buffer, rewind, or restart the stream.
1824
for verb, required_version in candidate_calls:
1825
if medium._is_remote_before(required_version):
1828
# We've already done the probing (and set _is_remote_before) on
1829
# a previous insert.
1832
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1834
response = client.call_with_body_stream(
1835
(verb, path, '') + lock_args, byte_stream)
1836
except errors.UnknownSmartMethod:
1837
medium._remember_remote_is_before(required_version)
1843
return self._insert_real(stream, src_format, resume_tokens)
1844
self._last_inv_record = None
1845
self._last_substream = None
1846
if required_version < (1, 19):
1847
# Remote side doesn't support inventory deltas. Wrap the stream to
1848
# make sure we don't send any. If the stream contains inventory
1849
# deltas we'll interrupt the smart insert_stream request and
1851
stream = self._stop_stream_if_inventory_delta(stream)
1852
byte_stream = smart_repo._stream_to_byte_stream(
1854
resume_tokens = ' '.join(resume_tokens)
1855
response = client.call_with_body_stream(
1856
(verb, path, resume_tokens) + lock_args, byte_stream)
1857
if response[0][0] not in ('ok', 'missing-basis'):
1858
raise errors.UnexpectedSmartServerResponse(response)
1859
if self._last_substream is not None:
1860
# The stream included an inventory-delta record, but the remote
1861
# side isn't new enough to support them. So we need to send the
1862
# rest of the stream via VFS.
1863
self.target_repo.refresh_data()
1864
return self._resume_stream_with_vfs(response, src_format)
1865
if response[0][0] == 'missing-basis':
1866
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1867
resume_tokens = tokens
1868
return resume_tokens, set(missing_keys)
1870
self.target_repo.refresh_data()
1873
def _resume_stream_with_vfs(self, response, src_format):
1874
"""Resume sending a stream via VFS, first resending the record and
1875
substream that couldn't be sent via an insert_stream verb.
1877
if response[0][0] == 'missing-basis':
1878
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1879
# Ignore missing_keys, we haven't finished inserting yet
1882
def resume_substream():
1883
# Yield the substream that was interrupted.
1884
for record in self._last_substream:
1886
self._last_substream = None
1887
def resume_stream():
1888
# Finish sending the interrupted substream
1889
yield ('inventory-deltas', resume_substream())
1890
# Then simply continue sending the rest of the stream.
1891
for substream_kind, substream in self._last_stream:
1892
yield substream_kind, substream
1893
return self._insert_real(resume_stream(), src_format, tokens)
1895
def _stop_stream_if_inventory_delta(self, stream):
1896
"""Normally this just lets the original stream pass-through unchanged.
1898
However if any 'inventory-deltas' substream occurs it will stop
1899
streaming, and store the interrupted substream and stream in
1900
self._last_substream and self._last_stream so that the stream can be
1901
resumed by _resume_stream_with_vfs.
1904
stream_iter = iter(stream)
1905
for substream_kind, substream in stream_iter:
1906
if substream_kind == 'inventory-deltas':
1907
self._last_substream = substream
1908
self._last_stream = stream_iter
1911
yield substream_kind, substream
1914
class RemoteStreamSource(repository.StreamSource):
1915
"""Stream data from a remote server."""
1917
def get_stream(self, search):
1918
if (self.from_repository._fallback_repositories and
1919
self.to_format._fetch_order == 'topological'):
1920
return self._real_stream(self.from_repository, search)
1923
repos = [self.from_repository]
1929
repos.extend(repo._fallback_repositories)
1930
sources.append(repo)
1931
return self.missing_parents_chain(search, sources)
1933
def get_stream_for_missing_keys(self, missing_keys):
1934
self.from_repository._ensure_real()
1935
real_repo = self.from_repository._real_repository
1936
real_source = real_repo._get_source(self.to_format)
1937
return real_source.get_stream_for_missing_keys(missing_keys)
1939
def _real_stream(self, repo, search):
1940
"""Get a stream for search from repo.
1942
This never called RemoteStreamSource.get_stream, and is a heler
1943
for RemoteStreamSource._get_stream to allow getting a stream
1944
reliably whether fallback back because of old servers or trying
1945
to stream from a non-RemoteRepository (which the stacked support
1948
source = repo._get_source(self.to_format)
1949
if isinstance(source, RemoteStreamSource):
1951
source = repo._real_repository._get_source(self.to_format)
1952
return source.get_stream(search)
1954
def _get_stream(self, repo, search):
1955
"""Core worker to get a stream from repo for search.
1957
This is used by both get_stream and the stacking support logic. It
1958
deliberately gets a stream for repo which does not need to be
1959
self.from_repository. In the event that repo is not Remote, or
1960
cannot do a smart stream, a fallback is made to the generic
1961
repository._get_stream() interface, via self._real_stream.
1963
In the event of stacking, streams from _get_stream will not
1964
contain all the data for search - this is normal (see get_stream).
1966
:param repo: A repository.
1967
:param search: A search.
1969
# Fallbacks may be non-smart
1970
if not isinstance(repo, RemoteRepository):
1971
return self._real_stream(repo, search)
1972
client = repo._client
1973
medium = client._medium
1974
path = repo.bzrdir._path_for_remote_call(client)
1975
search_bytes = repo._serialise_search_result(search)
1976
args = (path, self.to_format.network_name())
1978
('Repository.get_stream_1.19', (1, 19)),
1979
('Repository.get_stream', (1, 13))]
1982
for verb, version in candidate_verbs:
1983
if medium._is_remote_before(version):
1986
response = repo._call_with_body_bytes_expecting_body(
1987
verb, args, search_bytes)
1988
except errors.UnknownSmartMethod:
1989
medium._remember_remote_is_before(version)
1990
except errors.UnknownErrorFromSmartServer, e:
1991
if isinstance(search, graph.EverythingResult):
1992
error_verb = e.error_from_smart_server.error_verb
1993
if error_verb == 'BadSearch':
1994
# Pre-2.4 servers don't support this sort of search.
1995
# XXX: perhaps falling back to VFS on BadSearch is a
1996
# good idea in general? It might provide a little bit
1997
# of protection against client-side bugs.
1998
medium._remember_remote_is_before((2, 4))
2002
response_tuple, response_handler = response
2006
return self._real_stream(repo, search)
2007
if response_tuple[0] != 'ok':
2008
raise errors.UnexpectedSmartServerResponse(response_tuple)
2009
byte_stream = response_handler.read_streamed_body()
2010
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2011
self._record_counter)
2012
if src_format.network_name() != repo._format.network_name():
2013
raise AssertionError(
2014
"Mismatched RemoteRepository and stream src %r, %r" % (
2015
src_format.network_name(), repo._format.network_name()))
2018
def missing_parents_chain(self, search, sources):
2019
"""Chain multiple streams together to handle stacking.
2021
:param search: The overall search to satisfy with streams.
2022
:param sources: A list of Repository objects to query.
2024
self.from_serialiser = self.from_repository._format._serializer
2025
self.seen_revs = set()
2026
self.referenced_revs = set()
2027
# If there are heads in the search, or the key count is > 0, we are not
2029
while not search.is_empty() and len(sources) > 1:
2030
source = sources.pop(0)
2031
stream = self._get_stream(source, search)
2032
for kind, substream in stream:
2033
if kind != 'revisions':
2034
yield kind, substream
2036
yield kind, self.missing_parents_rev_handler(substream)
2037
search = search.refine(self.seen_revs, self.referenced_revs)
2038
self.seen_revs = set()
2039
self.referenced_revs = set()
2040
if not search.is_empty():
2041
for kind, stream in self._get_stream(sources[0], search):
2044
def missing_parents_rev_handler(self, substream):
2045
for content in substream:
2046
revision_bytes = content.get_bytes_as('fulltext')
2047
revision = self.from_serialiser.read_revision_from_string(
2049
self.seen_revs.add(content.key[-1])
2050
self.referenced_revs.update(revision.parent_ids)
2054
class RemoteBranchLockableFiles(LockableFiles):
2055
"""A 'LockableFiles' implementation that talks to a smart server.
2057
This is not a public interface class.
2060
def __init__(self, bzrdir, _client):
2061
self.bzrdir = bzrdir
2062
self._client = _client
2063
self._need_find_modes = True
2064
LockableFiles.__init__(
2065
self, bzrdir.get_branch_transport(None),
2066
'lock', lockdir.LockDir)
2068
def _find_modes(self):
2069
# RemoteBranches don't let the client set the mode of control files.
2070
self._dir_mode = None
2071
self._file_mode = None
2074
class RemoteBranchFormat(branch.BranchFormat):
2076
def __init__(self, network_name=None):
2077
super(RemoteBranchFormat, self).__init__()
2078
self._matchingbzrdir = RemoteBzrDirFormat()
2079
self._matchingbzrdir.set_branch_format(self)
2080
self._custom_format = None
2081
self._network_name = network_name
2083
def __eq__(self, other):
2084
return (isinstance(other, RemoteBranchFormat) and
2085
self.__dict__ == other.__dict__)
2087
def _ensure_real(self):
2088
if self._custom_format is None:
2089
self._custom_format = branch.network_format_registry.get(
2092
def get_format_description(self):
2094
return 'Remote: ' + self._custom_format.get_format_description()
2096
def network_name(self):
2097
return self._network_name
2099
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2100
return a_bzrdir.open_branch(name=name,
2101
ignore_fallbacks=ignore_fallbacks)
2103
def _vfs_initialize(self, a_bzrdir, name):
2104
# Initialisation when using a local bzrdir object, or a non-vfs init
2105
# method is not available on the server.
2106
# self._custom_format is always set - the start of initialize ensures
2108
if isinstance(a_bzrdir, RemoteBzrDir):
2109
a_bzrdir._ensure_real()
2110
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2113
# We assume the bzrdir is parameterised; it may not be.
2114
result = self._custom_format.initialize(a_bzrdir, name)
2115
if (isinstance(a_bzrdir, RemoteBzrDir) and
2116
not isinstance(result, RemoteBranch)):
2117
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2121
def initialize(self, a_bzrdir, name=None, repository=None):
2122
# 1) get the network name to use.
2123
if self._custom_format:
2124
network_name = self._custom_format.network_name()
2126
# Select the current bzrlib default and ask for that.
2127
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2128
reference_format = reference_bzrdir_format.get_branch_format()
2129
self._custom_format = reference_format
2130
network_name = reference_format.network_name()
2131
# Being asked to create on a non RemoteBzrDir:
2132
if not isinstance(a_bzrdir, RemoteBzrDir):
2133
return self._vfs_initialize(a_bzrdir, name=name)
2134
medium = a_bzrdir._client._medium
2135
if medium._is_remote_before((1, 13)):
2136
return self._vfs_initialize(a_bzrdir, name=name)
2137
# Creating on a remote bzr dir.
2138
# 2) try direct creation via RPC
2139
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2140
if name is not None:
2141
# XXX JRV20100304: Support creating colocated branches
2142
raise errors.NoColocatedBranchSupport(self)
2143
verb = 'BzrDir.create_branch'
2145
response = a_bzrdir._call(verb, path, network_name)
2146
except errors.UnknownSmartMethod:
2147
# Fallback - use vfs methods
2148
medium._remember_remote_is_before((1, 13))
2149
return self._vfs_initialize(a_bzrdir, name=name)
2150
if response[0] != 'ok':
2151
raise errors.UnexpectedSmartServerResponse(response)
2152
# Turn the response into a RemoteRepository object.
2153
format = RemoteBranchFormat(network_name=response[1])
2154
repo_format = response_tuple_to_repo_format(response[3:])
2155
repo_path = response[2]
2156
if repository is not None:
2157
remote_repo_url = urlutils.join(medium.base, repo_path)
2158
url_diff = urlutils.relative_url(repository.user_url,
2161
raise AssertionError(
2162
'repository.user_url %r does not match URL from server '
2163
'response (%r + %r)'
2164
% (repository.user_url, medium.base, repo_path))
2165
remote_repo = repository
2168
repo_bzrdir = a_bzrdir
2170
repo_bzrdir = RemoteBzrDir(
2171
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2173
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2174
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2175
format=format, setup_stacking=False, name=name)
2176
# XXX: We know this is a new branch, so it must have revno 0, revid
2177
# NULL_REVISION. Creating the branch locked would make this be unable
2178
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2179
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2180
return remote_branch
2182
def make_tags(self, branch):
2184
return self._custom_format.make_tags(branch)
2186
def supports_tags(self):
2187
# Remote branches might support tags, but we won't know until we
2188
# access the real remote branch.
2190
return self._custom_format.supports_tags()
2192
def supports_stacking(self):
2194
return self._custom_format.supports_stacking()
2196
def supports_set_append_revisions_only(self):
2198
return self._custom_format.supports_set_append_revisions_only()
2201
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2202
"""Branch stored on a server accessed by HPSS RPC.
2204
At the moment most operations are mapped down to simple file operations.
2207
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2208
_client=None, format=None, setup_stacking=True, name=None):
2209
"""Create a RemoteBranch instance.
2211
:param real_branch: An optional local implementation of the branch
2212
format, usually accessing the data via the VFS.
2213
:param _client: Private parameter for testing.
2214
:param format: A RemoteBranchFormat object, None to create one
2215
automatically. If supplied it should have a network_name already
2217
:param setup_stacking: If True make an RPC call to determine the
2218
stacked (or not) status of the branch. If False assume the branch
2220
:param name: Colocated branch name
2222
# We intentionally don't call the parent class's __init__, because it
2223
# will try to assign to self.tags, which is a property in this subclass.
2224
# And the parent's __init__ doesn't do much anyway.
2225
self.bzrdir = remote_bzrdir
2226
if _client is not None:
2227
self._client = _client
2229
self._client = remote_bzrdir._client
2230
self.repository = remote_repository
2231
if real_branch is not None:
2232
self._real_branch = real_branch
2233
# Give the remote repository the matching real repo.
2234
real_repo = self._real_branch.repository
2235
if isinstance(real_repo, RemoteRepository):
2236
real_repo._ensure_real()
2237
real_repo = real_repo._real_repository
2238
self.repository._set_real_repository(real_repo)
2239
# Give the branch the remote repository to let fast-pathing happen.
2240
self._real_branch.repository = self.repository
2242
self._real_branch = None
2243
# Fill out expected attributes of branch for bzrlib API users.
2244
self._clear_cached_state()
2245
# TODO: deprecate self.base in favor of user_url
2246
self.base = self.bzrdir.user_url
2248
self._control_files = None
2249
self._lock_mode = None
2250
self._lock_token = None
2251
self._repo_lock_token = None
2252
self._lock_count = 0
2253
self._leave_lock = False
2254
# Setup a format: note that we cannot call _ensure_real until all the
2255
# attributes above are set: This code cannot be moved higher up in this
2258
self._format = RemoteBranchFormat()
2259
if real_branch is not None:
2260
self._format._network_name = \
2261
self._real_branch._format.network_name()
2263
self._format = format
2264
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2265
# branch.open_branch method.
2266
self._real_ignore_fallbacks = not setup_stacking
2267
if not self._format._network_name:
2268
# Did not get from open_branchV2 - old server.
2270
self._format._network_name = \
2271
self._real_branch._format.network_name()
2272
self.tags = self._format.make_tags(self)
2273
# The base class init is not called, so we duplicate this:
2274
hooks = branch.Branch.hooks['open']
2277
self._is_stacked = False
2279
self._setup_stacking()
2281
def _setup_stacking(self):
2282
# configure stacking into the remote repository, by reading it from
2285
fallback_url = self.get_stacked_on_url()
2286
except (errors.NotStacked, errors.UnstackableBranchFormat,
2287
errors.UnstackableRepositoryFormat), e:
2289
self._is_stacked = True
2290
self._activate_fallback_location(fallback_url)
2292
def _get_config(self):
2293
return RemoteBranchConfig(self)
2295
def _get_real_transport(self):
2296
# if we try vfs access, return the real branch's vfs transport
2298
return self._real_branch._transport
2300
_transport = property(_get_real_transport)
2303
return "%s(%s)" % (self.__class__.__name__, self.base)
2307
def _ensure_real(self):
2308
"""Ensure that there is a _real_branch set.
2310
Used before calls to self._real_branch.
2312
if self._real_branch is None:
2313
if not vfs.vfs_enabled():
2314
raise AssertionError('smart server vfs must be enabled '
2315
'to use vfs implementation')
2316
self.bzrdir._ensure_real()
2317
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2318
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2319
if self.repository._real_repository is None:
2320
# Give the remote repository the matching real repo.
2321
real_repo = self._real_branch.repository
2322
if isinstance(real_repo, RemoteRepository):
2323
real_repo._ensure_real()
2324
real_repo = real_repo._real_repository
2325
self.repository._set_real_repository(real_repo)
2326
# Give the real branch the remote repository to let fast-pathing
2328
self._real_branch.repository = self.repository
2329
if self._lock_mode == 'r':
2330
self._real_branch.lock_read()
2331
elif self._lock_mode == 'w':
2332
self._real_branch.lock_write(token=self._lock_token)
2334
def _translate_error(self, err, **context):
2335
self.repository._translate_error(err, branch=self, **context)
2337
def _clear_cached_state(self):
2338
super(RemoteBranch, self)._clear_cached_state()
2339
if self._real_branch is not None:
2340
self._real_branch._clear_cached_state()
2342
def _clear_cached_state_of_remote_branch_only(self):
2343
"""Like _clear_cached_state, but doesn't clear the cache of
2346
This is useful when falling back to calling a method of
2347
self._real_branch that changes state. In that case the underlying
2348
branch changes, so we need to invalidate this RemoteBranch's cache of
2349
it. However, there's no need to invalidate the _real_branch's cache
2350
too, in fact doing so might harm performance.
2352
super(RemoteBranch, self)._clear_cached_state()
2355
def control_files(self):
2356
# Defer actually creating RemoteBranchLockableFiles until its needed,
2357
# because it triggers an _ensure_real that we otherwise might not need.
2358
if self._control_files is None:
2359
self._control_files = RemoteBranchLockableFiles(
2360
self.bzrdir, self._client)
2361
return self._control_files
2363
def _get_checkout_format(self):
2365
return self._real_branch._get_checkout_format()
2367
def get_physical_lock_status(self):
2368
"""See Branch.get_physical_lock_status()."""
2369
# should be an API call to the server, as branches must be lockable.
2371
return self._real_branch.get_physical_lock_status()
2373
def get_stacked_on_url(self):
2374
"""Get the URL this branch is stacked against.
2376
:raises NotStacked: If the branch is not stacked.
2377
:raises UnstackableBranchFormat: If the branch does not support
2379
:raises UnstackableRepositoryFormat: If the repository does not support
2383
# there may not be a repository yet, so we can't use
2384
# self._translate_error, so we can't use self._call either.
2385
response = self._client.call('Branch.get_stacked_on_url',
2386
self._remote_path())
2387
except errors.ErrorFromSmartServer, err:
2388
# there may not be a repository yet, so we can't call through
2389
# its _translate_error
2390
_translate_error(err, branch=self)
2391
except errors.UnknownSmartMethod, err:
2393
return self._real_branch.get_stacked_on_url()
2394
if response[0] != 'ok':
2395
raise errors.UnexpectedSmartServerResponse(response)
2398
def set_stacked_on_url(self, url):
2399
branch.Branch.set_stacked_on_url(self, url)
2401
self._is_stacked = False
2403
self._is_stacked = True
2405
def _vfs_get_tags_bytes(self):
2407
return self._real_branch._get_tags_bytes()
2410
def _get_tags_bytes(self):
2411
if self._tags_bytes is None:
2412
self._tags_bytes = self._get_tags_bytes_via_hpss()
2413
return self._tags_bytes
2415
def _get_tags_bytes_via_hpss(self):
2416
medium = self._client._medium
2417
if medium._is_remote_before((1, 13)):
2418
return self._vfs_get_tags_bytes()
2420
response = self._call('Branch.get_tags_bytes', self._remote_path())
2421
except errors.UnknownSmartMethod:
2422
medium._remember_remote_is_before((1, 13))
2423
return self._vfs_get_tags_bytes()
2426
def _vfs_set_tags_bytes(self, bytes):
2428
return self._real_branch._set_tags_bytes(bytes)
2430
def _set_tags_bytes(self, bytes):
2431
if self.is_locked():
2432
self._tags_bytes = bytes
2433
medium = self._client._medium
2434
if medium._is_remote_before((1, 18)):
2435
self._vfs_set_tags_bytes(bytes)
2439
self._remote_path(), self._lock_token, self._repo_lock_token)
2440
response = self._call_with_body_bytes(
2441
'Branch.set_tags_bytes', args, bytes)
2442
except errors.UnknownSmartMethod:
2443
medium._remember_remote_is_before((1, 18))
2444
self._vfs_set_tags_bytes(bytes)
2446
def lock_read(self):
2447
"""Lock the branch for read operations.
2449
:return: A bzrlib.lock.LogicalLockResult.
2451
self.repository.lock_read()
2452
if not self._lock_mode:
2453
self._note_lock('r')
2454
self._lock_mode = 'r'
2455
self._lock_count = 1
2456
if self._real_branch is not None:
2457
self._real_branch.lock_read()
2459
self._lock_count += 1
2460
return lock.LogicalLockResult(self.unlock)
2462
def _remote_lock_write(self, token):
2464
branch_token = repo_token = ''
2466
branch_token = token
2467
repo_token = self.repository.lock_write().repository_token
2468
self.repository.unlock()
2469
err_context = {'token': token}
2471
response = self._call(
2472
'Branch.lock_write', self._remote_path(), branch_token,
2473
repo_token or '', **err_context)
2474
except errors.LockContention, e:
2475
# The LockContention from the server doesn't have any
2476
# information about the lock_url. We re-raise LockContention
2477
# with valid lock_url.
2478
raise errors.LockContention('(remote lock)',
2479
self.repository.base.split('.bzr/')[0])
2480
if response[0] != 'ok':
2481
raise errors.UnexpectedSmartServerResponse(response)
2482
ok, branch_token, repo_token = response
2483
return branch_token, repo_token
2485
def lock_write(self, token=None):
2486
if not self._lock_mode:
2487
self._note_lock('w')
2488
# Lock the branch and repo in one remote call.
2489
remote_tokens = self._remote_lock_write(token)
2490
self._lock_token, self._repo_lock_token = remote_tokens
2491
if not self._lock_token:
2492
raise SmartProtocolError('Remote server did not return a token!')
2493
# Tell the self.repository object that it is locked.
2494
self.repository.lock_write(
2495
self._repo_lock_token, _skip_rpc=True)
2497
if self._real_branch is not None:
2498
self._real_branch.lock_write(token=self._lock_token)
2499
if token is not None:
2500
self._leave_lock = True
2502
self._leave_lock = False
2503
self._lock_mode = 'w'
2504
self._lock_count = 1
2505
elif self._lock_mode == 'r':
2506
raise errors.ReadOnlyError(self)
2508
if token is not None:
2509
# A token was given to lock_write, and we're relocking, so
2510
# check that the given token actually matches the one we
2512
if token != self._lock_token:
2513
raise errors.TokenMismatch(token, self._lock_token)
2514
self._lock_count += 1
2515
# Re-lock the repository too.
2516
self.repository.lock_write(self._repo_lock_token)
2517
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2519
def _unlock(self, branch_token, repo_token):
2520
err_context = {'token': str((branch_token, repo_token))}
2521
response = self._call(
2522
'Branch.unlock', self._remote_path(), branch_token,
2523
repo_token or '', **err_context)
2524
if response == ('ok',):
2526
raise errors.UnexpectedSmartServerResponse(response)
2528
@only_raises(errors.LockNotHeld, errors.LockBroken)
2531
self._lock_count -= 1
2532
if not self._lock_count:
2533
self._clear_cached_state()
2534
mode = self._lock_mode
2535
self._lock_mode = None
2536
if self._real_branch is not None:
2537
if (not self._leave_lock and mode == 'w' and
2538
self._repo_lock_token):
2539
# If this RemoteBranch will remove the physical lock
2540
# for the repository, make sure the _real_branch
2541
# doesn't do it first. (Because the _real_branch's
2542
# repository is set to be the RemoteRepository.)
2543
self._real_branch.repository.leave_lock_in_place()
2544
self._real_branch.unlock()
2546
# Only write-locked branched need to make a remote method
2547
# call to perform the unlock.
2549
if not self._lock_token:
2550
raise AssertionError('Locked, but no token!')
2551
branch_token = self._lock_token
2552
repo_token = self._repo_lock_token
2553
self._lock_token = None
2554
self._repo_lock_token = None
2555
if not self._leave_lock:
2556
self._unlock(branch_token, repo_token)
2558
self.repository.unlock()
2560
def break_lock(self):
2562
return self._real_branch.break_lock()
2564
def leave_lock_in_place(self):
2565
if not self._lock_token:
2566
raise NotImplementedError(self.leave_lock_in_place)
2567
self._leave_lock = True
2569
def dont_leave_lock_in_place(self):
2570
if not self._lock_token:
2571
raise NotImplementedError(self.dont_leave_lock_in_place)
2572
self._leave_lock = False
2575
def get_rev_id(self, revno, history=None):
2577
return _mod_revision.NULL_REVISION
2578
last_revision_info = self.last_revision_info()
2579
ok, result = self.repository.get_rev_id_for_revno(
2580
revno, last_revision_info)
2583
missing_parent = result[1]
2584
# Either the revision named by the server is missing, or its parent
2585
# is. Call get_parent_map to determine which, so that we report a
2587
parent_map = self.repository.get_parent_map([missing_parent])
2588
if missing_parent in parent_map:
2589
missing_parent = parent_map[missing_parent]
2590
raise errors.RevisionNotPresent(missing_parent, self.repository)
2592
def _last_revision_info(self):
2593
response = self._call('Branch.last_revision_info', self._remote_path())
2594
if response[0] != 'ok':
2595
raise SmartProtocolError('unexpected response code %s' % (response,))
2596
revno = int(response[1])
2597
last_revision = response[2]
2598
return (revno, last_revision)
2600
def _gen_revision_history(self):
2601
"""See Branch._gen_revision_history()."""
2602
if self._is_stacked:
2604
return self._real_branch._gen_revision_history()
2605
response_tuple, response_handler = self._call_expecting_body(
2606
'Branch.revision_history', self._remote_path())
2607
if response_tuple[0] != 'ok':
2608
raise errors.UnexpectedSmartServerResponse(response_tuple)
2609
result = response_handler.read_body_bytes().split('\x00')
2614
def _remote_path(self):
2615
return self.bzrdir._path_for_remote_call(self._client)
2617
def _set_last_revision_descendant(self, revision_id, other_branch,
2618
allow_diverged=False, allow_overwrite_descendant=False):
2619
# This performs additional work to meet the hook contract; while its
2620
# undesirable, we have to synthesise the revno to call the hook, and
2621
# not calling the hook is worse as it means changes can't be prevented.
2622
# Having calculated this though, we can't just call into
2623
# set_last_revision_info as a simple call, because there is a set_rh
2624
# hook that some folk may still be using.
2625
old_revno, old_revid = self.last_revision_info()
2626
history = self._lefthand_history(revision_id)
2627
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2628
err_context = {'other_branch': other_branch}
2629
response = self._call('Branch.set_last_revision_ex',
2630
self._remote_path(), self._lock_token, self._repo_lock_token,
2631
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2633
self._clear_cached_state()
2634
if len(response) != 3 and response[0] != 'ok':
2635
raise errors.UnexpectedSmartServerResponse(response)
2636
new_revno, new_revision_id = response[1:]
2637
self._last_revision_info_cache = new_revno, new_revision_id
2638
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2639
if self._real_branch is not None:
2640
cache = new_revno, new_revision_id
2641
self._real_branch._last_revision_info_cache = cache
2643
def _set_last_revision(self, revision_id):
2644
old_revno, old_revid = self.last_revision_info()
2645
# This performs additional work to meet the hook contract; while its
2646
# undesirable, we have to synthesise the revno to call the hook, and
2647
# not calling the hook is worse as it means changes can't be prevented.
2648
# Having calculated this though, we can't just call into
2649
# set_last_revision_info as a simple call, because there is a set_rh
2650
# hook that some folk may still be using.
2651
history = self._lefthand_history(revision_id)
2652
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2653
self._clear_cached_state()
2654
response = self._call('Branch.set_last_revision',
2655
self._remote_path(), self._lock_token, self._repo_lock_token,
2657
if response != ('ok',):
2658
raise errors.UnexpectedSmartServerResponse(response)
2659
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2662
def set_revision_history(self, rev_history):
2663
# Send just the tip revision of the history; the server will generate
2664
# the full history from that. If the revision doesn't exist in this
2665
# branch, NoSuchRevision will be raised.
2666
if rev_history == []:
2669
rev_id = rev_history[-1]
2670
self._set_last_revision(rev_id)
2671
for hook in branch.Branch.hooks['set_rh']:
2672
hook(self, rev_history)
2673
self._cache_revision_history(rev_history)
2675
def _get_parent_location(self):
2676
medium = self._client._medium
2677
if medium._is_remote_before((1, 13)):
2678
return self._vfs_get_parent_location()
2680
response = self._call('Branch.get_parent', self._remote_path())
2681
except errors.UnknownSmartMethod:
2682
medium._remember_remote_is_before((1, 13))
2683
return self._vfs_get_parent_location()
2684
if len(response) != 1:
2685
raise errors.UnexpectedSmartServerResponse(response)
2686
parent_location = response[0]
2687
if parent_location == '':
2689
return parent_location
2691
def _vfs_get_parent_location(self):
2693
return self._real_branch._get_parent_location()
2695
def _set_parent_location(self, url):
2696
medium = self._client._medium
2697
if medium._is_remote_before((1, 15)):
2698
return self._vfs_set_parent_location(url)
2700
call_url = url or ''
2701
if type(call_url) is not str:
2702
raise AssertionError('url must be a str or None (%s)' % url)
2703
response = self._call('Branch.set_parent_location',
2704
self._remote_path(), self._lock_token, self._repo_lock_token,
2706
except errors.UnknownSmartMethod:
2707
medium._remember_remote_is_before((1, 15))
2708
return self._vfs_set_parent_location(url)
2710
raise errors.UnexpectedSmartServerResponse(response)
2712
def _vfs_set_parent_location(self, url):
2714
return self._real_branch._set_parent_location(url)
2717
def pull(self, source, overwrite=False, stop_revision=None,
2719
self._clear_cached_state_of_remote_branch_only()
2721
return self._real_branch.pull(
2722
source, overwrite=overwrite, stop_revision=stop_revision,
2723
_override_hook_target=self, **kwargs)
2726
def push(self, target, overwrite=False, stop_revision=None):
2728
return self._real_branch.push(
2729
target, overwrite=overwrite, stop_revision=stop_revision,
2730
_override_hook_source_branch=self)
2732
def is_locked(self):
2733
return self._lock_count >= 1
2736
def revision_id_to_revno(self, revision_id):
2738
return self._real_branch.revision_id_to_revno(revision_id)
2741
def set_last_revision_info(self, revno, revision_id):
2742
# XXX: These should be returned by the set_last_revision_info verb
2743
old_revno, old_revid = self.last_revision_info()
2744
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2745
revision_id = ensure_null(revision_id)
2747
response = self._call('Branch.set_last_revision_info',
2748
self._remote_path(), self._lock_token, self._repo_lock_token,
2749
str(revno), revision_id)
2750
except errors.UnknownSmartMethod:
2752
self._clear_cached_state_of_remote_branch_only()
2753
self._real_branch.set_last_revision_info(revno, revision_id)
2754
self._last_revision_info_cache = revno, revision_id
2756
if response == ('ok',):
2757
self._clear_cached_state()
2758
self._last_revision_info_cache = revno, revision_id
2759
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2760
# Update the _real_branch's cache too.
2761
if self._real_branch is not None:
2762
cache = self._last_revision_info_cache
2763
self._real_branch._last_revision_info_cache = cache
2765
raise errors.UnexpectedSmartServerResponse(response)
2768
def generate_revision_history(self, revision_id, last_rev=None,
2770
medium = self._client._medium
2771
if not medium._is_remote_before((1, 6)):
2772
# Use a smart method for 1.6 and above servers
2774
self._set_last_revision_descendant(revision_id, other_branch,
2775
allow_diverged=True, allow_overwrite_descendant=True)
2777
except errors.UnknownSmartMethod:
2778
medium._remember_remote_is_before((1, 6))
2779
self._clear_cached_state_of_remote_branch_only()
2780
self.set_revision_history(self._lefthand_history(revision_id,
2781
last_rev=last_rev,other_branch=other_branch))
2783
def set_push_location(self, location):
2785
return self._real_branch.set_push_location(location)
2788
class RemoteConfig(object):
2789
"""A Config that reads and writes from smart verbs.
2791
It is a low-level object that considers config data to be name/value pairs
2792
that may be associated with a section. Assigning meaning to the these
2793
values is done at higher levels like bzrlib.config.TreeConfig.
2796
def get_option(self, name, section=None, default=None):
2797
"""Return the value associated with a named option.
2799
:param name: The name of the value
2800
:param section: The section the option is in (if any)
2801
:param default: The value to return if the value is not set
2802
:return: The value or default value
2805
configobj = self._get_configobj()
2807
section_obj = configobj
2810
section_obj = configobj[section]
2813
return section_obj.get(name, default)
2814
except errors.UnknownSmartMethod:
2815
return self._vfs_get_option(name, section, default)
2817
def _response_to_configobj(self, response):
2818
if len(response[0]) and response[0][0] != 'ok':
2819
raise errors.UnexpectedSmartServerResponse(response)
2820
lines = response[1].read_body_bytes().splitlines()
2821
return config.ConfigObj(lines, encoding='utf-8')
2824
class RemoteBranchConfig(RemoteConfig):
2825
"""A RemoteConfig for Branches."""
2827
def __init__(self, branch):
2828
self._branch = branch
2830
def _get_configobj(self):
2831
path = self._branch._remote_path()
2832
response = self._branch._client.call_expecting_body(
2833
'Branch.get_config_file', path)
2834
return self._response_to_configobj(response)
2836
def set_option(self, value, name, section=None):
2837
"""Set the value associated with a named option.
2839
:param value: The value to set
2840
:param name: The name of the value to set
2841
:param section: The section the option is in (if any)
2843
medium = self._branch._client._medium
2844
if medium._is_remote_before((1, 14)):
2845
return self._vfs_set_option(value, name, section)
2846
if isinstance(value, dict):
2847
if medium._is_remote_before((2, 2)):
2848
return self._vfs_set_option(value, name, section)
2849
return self._set_config_option_dict(value, name, section)
2851
return self._set_config_option(value, name, section)
2853
def _set_config_option(self, value, name, section):
2855
path = self._branch._remote_path()
2856
response = self._branch._client.call('Branch.set_config_option',
2857
path, self._branch._lock_token, self._branch._repo_lock_token,
2858
value.encode('utf8'), name, section or '')
2859
except errors.UnknownSmartMethod:
2860
medium = self._branch._client._medium
2861
medium._remember_remote_is_before((1, 14))
2862
return self._vfs_set_option(value, name, section)
2864
raise errors.UnexpectedSmartServerResponse(response)
2866
def _serialize_option_dict(self, option_dict):
2868
for key, value in option_dict.items():
2869
if isinstance(key, unicode):
2870
key = key.encode('utf8')
2871
if isinstance(value, unicode):
2872
value = value.encode('utf8')
2873
utf8_dict[key] = value
2874
return bencode.bencode(utf8_dict)
2876
def _set_config_option_dict(self, value, name, section):
2878
path = self._branch._remote_path()
2879
serialised_dict = self._serialize_option_dict(value)
2880
response = self._branch._client.call(
2881
'Branch.set_config_option_dict',
2882
path, self._branch._lock_token, self._branch._repo_lock_token,
2883
serialised_dict, name, section or '')
2884
except errors.UnknownSmartMethod:
2885
medium = self._branch._client._medium
2886
medium._remember_remote_is_before((2, 2))
2887
return self._vfs_set_option(value, name, section)
2889
raise errors.UnexpectedSmartServerResponse(response)
2891
def _real_object(self):
2892
self._branch._ensure_real()
2893
return self._branch._real_branch
2895
def _vfs_set_option(self, value, name, section=None):
2896
return self._real_object()._get_config().set_option(
2897
value, name, section)
2900
class RemoteBzrDirConfig(RemoteConfig):
2901
"""A RemoteConfig for BzrDirs."""
2903
def __init__(self, bzrdir):
2904
self._bzrdir = bzrdir
2906
def _get_configobj(self):
2907
medium = self._bzrdir._client._medium
2908
verb = 'BzrDir.get_config_file'
2909
if medium._is_remote_before((1, 15)):
2910
raise errors.UnknownSmartMethod(verb)
2911
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2912
response = self._bzrdir._call_expecting_body(
2914
return self._response_to_configobj(response)
2916
def _vfs_get_option(self, name, section, default):
2917
return self._real_object()._get_config().get_option(
2918
name, section, default)
2920
def set_option(self, value, name, section=None):
2921
"""Set the value associated with a named option.
2923
:param value: The value to set
2924
:param name: The name of the value to set
2925
:param section: The section the option is in (if any)
2927
return self._real_object()._get_config().set_option(
2928
value, name, section)
2930
def _real_object(self):
2931
self._bzrdir._ensure_real()
2932
return self._bzrdir._real_bzrdir
2936
def _extract_tar(tar, to_dir):
2937
"""Extract all the contents of a tarfile object.
2939
A replacement for extractall, which is not present in python2.4
2942
tar.extract(tarinfo, to_dir)
2945
def _translate_error(err, **context):
2946
"""Translate an ErrorFromSmartServer into a more useful error.
2948
Possible context keys:
2956
If the error from the server doesn't match a known pattern, then
2957
UnknownErrorFromSmartServer is raised.
2961
return context[name]
2962
except KeyError, key_err:
2963
mutter('Missing key %r in context %r', key_err.args[0], context)
2966
"""Get the path from the context if present, otherwise use first error
2970
return context['path']
2971
except KeyError, key_err:
2973
return err.error_args[0]
2974
except IndexError, idx_err:
2976
'Missing key %r in context %r', key_err.args[0], context)
2979
if err.error_verb == 'IncompatibleRepositories':
2980
raise errors.IncompatibleRepositories(err.error_args[0],
2981
err.error_args[1], err.error_args[2])
2982
elif err.error_verb == 'NoSuchRevision':
2983
raise NoSuchRevision(find('branch'), err.error_args[0])
2984
elif err.error_verb == 'nosuchrevision':
2985
raise NoSuchRevision(find('repository'), err.error_args[0])
2986
elif err.error_verb == 'nobranch':
2987
if len(err.error_args) >= 1:
2988
extra = err.error_args[0]
2991
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2993
elif err.error_verb == 'norepository':
2994
raise errors.NoRepositoryPresent(find('bzrdir'))
2995
elif err.error_verb == 'LockContention':
2996
raise errors.LockContention('(remote lock)')
2997
elif err.error_verb == 'UnlockableTransport':
2998
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2999
elif err.error_verb == 'LockFailed':
3000
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3001
elif err.error_verb == 'TokenMismatch':
3002
raise errors.TokenMismatch(find('token'), '(remote token)')
3003
elif err.error_verb == 'Diverged':
3004
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3005
elif err.error_verb == 'TipChangeRejected':
3006
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3007
elif err.error_verb == 'UnstackableBranchFormat':
3008
raise errors.UnstackableBranchFormat(*err.error_args)
3009
elif err.error_verb == 'UnstackableRepositoryFormat':
3010
raise errors.UnstackableRepositoryFormat(*err.error_args)
3011
elif err.error_verb == 'NotStacked':
3012
raise errors.NotStacked(branch=find('branch'))
3013
elif err.error_verb == 'PermissionDenied':
3015
if len(err.error_args) >= 2:
3016
extra = err.error_args[1]
3019
raise errors.PermissionDenied(path, extra=extra)
3020
elif err.error_verb == 'ReadError':
3022
raise errors.ReadError(path)
3023
elif err.error_verb == 'NoSuchFile':
3025
raise errors.NoSuchFile(path)
3026
elif err.error_verb == 'FileExists':
3027
raise errors.FileExists(err.error_args[0])
3028
elif err.error_verb == 'DirectoryNotEmpty':
3029
raise errors.DirectoryNotEmpty(err.error_args[0])
3030
elif err.error_verb == 'ShortReadvError':
3031
args = err.error_args
3032
raise errors.ShortReadvError(
3033
args[0], int(args[1]), int(args[2]), int(args[3]))
3034
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3035
encoding = str(err.error_args[0]) # encoding must always be a string
3036
val = err.error_args[1]
3037
start = int(err.error_args[2])
3038
end = int(err.error_args[3])
3039
reason = str(err.error_args[4]) # reason must always be a string
3040
if val.startswith('u:'):
3041
val = val[2:].decode('utf-8')
3042
elif val.startswith('s:'):
3043
val = val[2:].decode('base64')
3044
if err.error_verb == 'UnicodeDecodeError':
3045
raise UnicodeDecodeError(encoding, val, start, end, reason)
3046
elif err.error_verb == 'UnicodeEncodeError':
3047
raise UnicodeEncodeError(encoding, val, start, end, reason)
3048
elif err.error_verb == 'ReadOnlyError':
3049
raise errors.TransportNotPossible('readonly transport')
3050
raise errors.UnknownErrorFromSmartServer(err)