1
# Copyright (C) 2006, 2007, 2008 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
35
from bzrlib.branch import BranchReferenceFormat
36
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
from bzrlib.errors import (
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.smart import client, vfs, repository as smart_repo
44
from bzrlib.revision import ensure_null, NULL_REVISION
45
from bzrlib.trace import mutter, note, warning
46
from bzrlib.util import bencode
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
67
return self._client.call_with_body_bytes_expecting_body(
68
method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
73
def response_tuple_to_repo_format(response):
74
"""Convert a response tuple describing a repository format to a format."""
75
format = RemoteRepositoryFormat()
76
format.rich_root_data = (response[0] == 'yes')
77
format.supports_tree_reference = (response[1] == 'yes')
78
format.supports_external_lookups = (response[2] == 'yes')
79
format._network_name = response[3]
83
# Note: RemoteBzrDirFormat is in bzrdir.py
85
class RemoteBzrDir(BzrDir, _RpcHelper):
86
"""Control directory on a remote server, accessed via bzr:// or similar."""
88
def __init__(self, transport, format, _client=None):
89
"""Construct a RemoteBzrDir.
91
:param _client: Private parameter for testing. Disables probing and the
94
BzrDir.__init__(self, transport, format)
95
# this object holds a delegated bzrdir that uses file-level operations
96
# to talk to the other side
97
self._real_bzrdir = None
98
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
# create_branch for details.
100
self._next_open_branch_result = None
103
medium = transport.get_smart_medium()
104
self._client = client._SmartClient(medium)
106
self._client = _client
109
path = self._path_for_remote_call(self._client)
110
response = self._call('BzrDir.open', path)
111
if response not in [('yes',), ('no',)]:
112
raise errors.UnexpectedSmartServerResponse(response)
113
if response == ('no',):
114
raise errors.NotBranchError(path=transport.base)
116
def _ensure_real(self):
117
"""Ensure that there is a _real_bzrdir set.
119
Used before calls to self._real_bzrdir.
121
if not self._real_bzrdir:
122
self._real_bzrdir = BzrDir.open_from_transport(
123
self.root_transport, _server_formats=False)
124
self._format._network_name = \
125
self._real_bzrdir._format.network_name()
127
def _translate_error(self, err, **context):
128
_translate_error(err, bzrdir=self, **context)
130
def break_lock(self):
131
# Prevent aliasing problems in the next_open_branch_result cache.
132
# See create_branch for rationale.
133
self._next_open_branch_result = None
134
return BzrDir.break_lock(self)
136
def _vfs_cloning_metadir(self, require_stacking=False):
138
return self._real_bzrdir.cloning_metadir(
139
require_stacking=require_stacking)
141
def cloning_metadir(self, require_stacking=False):
142
medium = self._client._medium
143
if medium._is_remote_before((1, 13)):
144
return self._vfs_cloning_metadir(require_stacking=require_stacking)
145
verb = 'BzrDir.cloning_metadir'
150
path = self._path_for_remote_call(self._client)
152
response = self._call(verb, path, stacking)
153
except errors.UnknownSmartMethod:
154
medium._remember_remote_is_before((1, 13))
155
return self._vfs_cloning_metadir(require_stacking=require_stacking)
156
if len(response) != 3:
157
raise errors.UnexpectedSmartServerResponse(response)
158
control_name, repo_name, branch_info = response
159
if len(branch_info) != 2:
160
raise errors.UnexpectedSmartServerResponse(response)
161
branch_ref, branch_name = branch_info
162
format = bzrdir.network_format_registry.get(control_name)
164
format.repository_format = repository.network_format_registry.get(
166
if branch_ref == 'ref':
167
# XXX: we need possible_transports here to avoid reopening the
168
# connection to the referenced location
169
ref_bzrdir = BzrDir.open(branch_name)
170
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
171
format.set_branch_format(branch_format)
172
elif branch_ref == 'branch':
174
format.set_branch_format(
175
branch.network_format_registry.get(branch_name))
177
raise errors.UnexpectedSmartServerResponse(response)
180
def create_repository(self, shared=False):
181
# as per meta1 formats - just delegate to the format object which may
183
result = self._format.repository_format.initialize(self, shared)
184
if not isinstance(result, RemoteRepository):
185
return self.open_repository()
189
def destroy_repository(self):
190
"""See BzrDir.destroy_repository"""
192
self._real_bzrdir.destroy_repository()
194
def create_branch(self):
195
# as per meta1 formats - just delegate to the format object which may
197
real_branch = self._format.get_branch_format().initialize(self)
198
if not isinstance(real_branch, RemoteBranch):
199
result = RemoteBranch(self, self.find_repository(), real_branch)
202
# BzrDir.clone_on_transport() uses the result of create_branch but does
203
# not return it to its callers; we save approximately 8% of our round
204
# trips by handing the branch we created back to the first caller to
205
# open_branch rather than probing anew. Long term we need a API in
206
# bzrdir that doesn't discard result objects (like result_branch).
208
self._next_open_branch_result = result
211
def destroy_branch(self):
212
"""See BzrDir.destroy_branch"""
214
self._real_bzrdir.destroy_branch()
215
self._next_open_branch_result = None
217
def create_workingtree(self, revision_id=None, from_branch=None):
218
raise errors.NotLocalUrl(self.transport.base)
220
def find_branch_format(self):
221
"""Find the branch 'format' for this bzrdir.
223
This might be a synthetic object for e.g. RemoteBranch and SVN.
225
b = self.open_branch()
228
def get_branch_reference(self):
229
"""See BzrDir.get_branch_reference()."""
230
response = self._get_branch_reference()
231
if response[0] == 'ref':
236
def _get_branch_reference(self):
237
path = self._path_for_remote_call(self._client)
238
medium = self._client._medium
239
if not medium._is_remote_before((1, 13)):
241
response = self._call('BzrDir.open_branchV2', path)
242
if response[0] not in ('ref', 'branch'):
243
raise errors.UnexpectedSmartServerResponse(response)
245
except errors.UnknownSmartMethod:
246
medium._remember_remote_is_before((1, 13))
247
response = self._call('BzrDir.open_branch', path)
248
if response[0] != 'ok':
249
raise errors.UnexpectedSmartServerResponse(response)
250
if response[1] != '':
251
return ('ref', response[1])
253
return ('branch', '')
255
def _get_tree_branch(self):
256
"""See BzrDir._get_tree_branch()."""
257
return None, self.open_branch()
259
def open_branch(self, _unsupported=False):
261
raise NotImplementedError('unsupported flag support not implemented yet.')
262
if self._next_open_branch_result is not None:
263
# See create_branch for details.
264
result = self._next_open_branch_result
265
self._next_open_branch_result = None
267
response = self._get_branch_reference()
268
if response[0] == 'ref':
269
# a branch reference, use the existing BranchReference logic.
270
format = BranchReferenceFormat()
271
return format.open(self, _found=True, location=response[1])
272
branch_format_name = response[1]
273
if not branch_format_name:
274
branch_format_name = None
275
format = RemoteBranchFormat(network_name=branch_format_name)
276
return RemoteBranch(self, self.find_repository(), format=format)
278
def _open_repo_v1(self, path):
279
verb = 'BzrDir.find_repository'
280
response = self._call(verb, path)
281
if response[0] != 'ok':
282
raise errors.UnexpectedSmartServerResponse(response)
283
# servers that only support the v1 method don't support external
286
repo = self._real_bzrdir.open_repository()
287
response = response + ('no', repo._format.network_name())
288
return response, repo
290
def _open_repo_v2(self, path):
291
verb = 'BzrDir.find_repositoryV2'
292
response = self._call(verb, path)
293
if response[0] != 'ok':
294
raise errors.UnexpectedSmartServerResponse(response)
296
repo = self._real_bzrdir.open_repository()
297
response = response + (repo._format.network_name(),)
298
return response, repo
300
def _open_repo_v3(self, path):
301
verb = 'BzrDir.find_repositoryV3'
302
medium = self._client._medium
303
if medium._is_remote_before((1, 13)):
304
raise errors.UnknownSmartMethod(verb)
306
response = self._call(verb, path)
307
except errors.UnknownSmartMethod:
308
medium._remember_remote_is_before((1, 13))
310
if response[0] != 'ok':
311
raise errors.UnexpectedSmartServerResponse(response)
312
return response, None
314
def open_repository(self):
315
path = self._path_for_remote_call(self._client)
317
for probe in [self._open_repo_v3, self._open_repo_v2,
320
response, real_repo = probe(path)
322
except errors.UnknownSmartMethod:
325
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
326
if response[0] != 'ok':
327
raise errors.UnexpectedSmartServerResponse(response)
328
if len(response) != 6:
329
raise SmartProtocolError('incorrect response length %s' % (response,))
330
if response[1] == '':
331
# repo is at this dir.
332
format = response_tuple_to_repo_format(response[2:])
333
# Used to support creating a real format instance when needed.
334
format._creating_bzrdir = self
335
remote_repo = RemoteRepository(self, format)
336
format._creating_repo = remote_repo
337
if real_repo is not None:
338
remote_repo._set_real_repository(real_repo)
341
raise errors.NoRepositoryPresent(self)
343
def open_workingtree(self, recommend_upgrade=True):
345
if self._real_bzrdir.has_workingtree():
346
raise errors.NotLocalUrl(self.root_transport)
348
raise errors.NoWorkingTree(self.root_transport.base)
350
def _path_for_remote_call(self, client):
351
"""Return the path to be used for this bzrdir in a remote call."""
352
return client.remote_path_from_transport(self.root_transport)
354
def get_branch_transport(self, branch_format):
356
return self._real_bzrdir.get_branch_transport(branch_format)
358
def get_repository_transport(self, repository_format):
360
return self._real_bzrdir.get_repository_transport(repository_format)
362
def get_workingtree_transport(self, workingtree_format):
364
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
366
def can_convert_format(self):
367
"""Upgrading of remote bzrdirs is not supported yet."""
370
def needs_format_conversion(self, format=None):
371
"""Upgrading of remote bzrdirs is not supported yet."""
373
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
374
% 'needs_format_conversion(format=None)')
377
def clone(self, url, revision_id=None, force_new_repo=False,
378
preserve_stacking=False):
380
return self._real_bzrdir.clone(url, revision_id=revision_id,
381
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
383
def get_config(self):
385
return self._real_bzrdir.get_config()
388
class RemoteRepositoryFormat(repository.RepositoryFormat):
389
"""Format for repositories accessed over a _SmartClient.
391
Instances of this repository are represented by RemoteRepository
394
The RemoteRepositoryFormat is parameterized during construction
395
to reflect the capabilities of the real, remote format. Specifically
396
the attributes rich_root_data and supports_tree_reference are set
397
on a per instance basis, and are not set (and should not be) at
400
:ivar _custom_format: If set, a specific concrete repository format that
401
will be used when initializing a repository with this
402
RemoteRepositoryFormat.
403
:ivar _creating_repo: If set, the repository object that this
404
RemoteRepositoryFormat was created for: it can be called into
405
to obtain data like the network name.
408
_matchingbzrdir = RemoteBzrDirFormat()
411
repository.RepositoryFormat.__init__(self)
412
self._custom_format = None
413
self._network_name = None
414
self._creating_bzrdir = None
416
def _vfs_initialize(self, a_bzrdir, shared):
417
"""Helper for common code in initialize."""
418
if self._custom_format:
419
# Custom format requested
420
result = self._custom_format.initialize(a_bzrdir, shared=shared)
421
elif self._creating_bzrdir is not None:
422
# Use the format that the repository we were created to back
424
prior_repo = self._creating_bzrdir.open_repository()
425
prior_repo._ensure_real()
426
result = prior_repo._real_repository._format.initialize(
427
a_bzrdir, shared=shared)
429
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
430
# support remote initialization.
431
# We delegate to a real object at this point (as RemoteBzrDir
432
# delegate to the repository format which would lead to infinite
433
# recursion if we just called a_bzrdir.create_repository.
434
a_bzrdir._ensure_real()
435
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
436
if not isinstance(result, RemoteRepository):
437
return self.open(a_bzrdir)
441
def initialize(self, a_bzrdir, shared=False):
442
# Being asked to create on a non RemoteBzrDir:
443
if not isinstance(a_bzrdir, RemoteBzrDir):
444
return self._vfs_initialize(a_bzrdir, shared)
445
medium = a_bzrdir._client._medium
446
if medium._is_remote_before((1, 13)):
447
return self._vfs_initialize(a_bzrdir, shared)
448
# Creating on a remote bzr dir.
449
# 1) get the network name to use.
450
if self._custom_format:
451
network_name = self._custom_format.network_name()
453
# Select the current bzrlib default and ask for that.
454
reference_bzrdir_format = bzrdir.format_registry.get('default')()
455
reference_format = reference_bzrdir_format.repository_format
456
network_name = reference_format.network_name()
457
# 2) try direct creation via RPC
458
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
459
verb = 'BzrDir.create_repository'
465
response = a_bzrdir._call(verb, path, network_name, shared_str)
466
except errors.UnknownSmartMethod:
467
# Fallback - use vfs methods
468
medium._remember_remote_is_before((1, 13))
469
return self._vfs_initialize(a_bzrdir, shared)
471
# Turn the response into a RemoteRepository object.
472
format = response_tuple_to_repo_format(response[1:])
473
# Used to support creating a real format instance when needed.
474
format._creating_bzrdir = a_bzrdir
475
remote_repo = RemoteRepository(a_bzrdir, format)
476
format._creating_repo = remote_repo
479
def open(self, a_bzrdir):
480
if not isinstance(a_bzrdir, RemoteBzrDir):
481
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
482
return a_bzrdir.open_repository()
484
def _ensure_real(self):
485
if self._custom_format is None:
486
self._custom_format = repository.network_format_registry.get(
490
def _fetch_order(self):
492
return self._custom_format._fetch_order
495
def _fetch_uses_deltas(self):
497
return self._custom_format._fetch_uses_deltas
500
def _fetch_reconcile(self):
502
return self._custom_format._fetch_reconcile
504
def get_format_description(self):
505
return 'bzr remote repository'
507
def __eq__(self, other):
508
return self.__class__ is other.__class__
510
def check_conversion_target(self, target_format):
511
if self.rich_root_data and not target_format.rich_root_data:
512
raise errors.BadConversionTarget(
513
'Does not support rich root data.', target_format)
514
if (self.supports_tree_reference and
515
not getattr(target_format, 'supports_tree_reference', False)):
516
raise errors.BadConversionTarget(
517
'Does not support nested trees', target_format)
519
def network_name(self):
520
if self._network_name:
521
return self._network_name
522
self._creating_repo._ensure_real()
523
return self._creating_repo._real_repository._format.network_name()
526
def _serializer(self):
528
return self._custom_format._serializer
531
class RemoteRepository(_RpcHelper):
532
"""Repository accessed over rpc.
534
For the moment most operations are performed using local transport-backed
538
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
539
"""Create a RemoteRepository instance.
541
:param remote_bzrdir: The bzrdir hosting this repository.
542
:param format: The RemoteFormat object to use.
543
:param real_repository: If not None, a local implementation of the
544
repository logic for the repository, usually accessing the data
546
:param _client: Private testing parameter - override the smart client
547
to be used by the repository.
550
self._real_repository = real_repository
552
self._real_repository = None
553
self.bzrdir = remote_bzrdir
555
self._client = remote_bzrdir._client
557
self._client = _client
558
self._format = format
559
self._lock_mode = None
560
self._lock_token = None
562
self._leave_lock = False
563
self._unstacked_provider = graph.CachingParentsProvider(
564
get_parent_map=self._get_parent_map_rpc)
565
self._unstacked_provider.disable_cache()
567
# These depend on the actual remote format, so force them off for
568
# maximum compatibility. XXX: In future these should depend on the
569
# remote repository instance, but this is irrelevant until we perform
570
# reconcile via an RPC call.
571
self._reconcile_does_inventory_gc = False
572
self._reconcile_fixes_text_parents = False
573
self._reconcile_backsup_inventory = False
574
self.base = self.bzrdir.transport.base
575
# Additional places to query for data.
576
self._fallback_repositories = []
579
return "%s(%s)" % (self.__class__.__name__, self.base)
583
def abort_write_group(self, suppress_errors=False):
584
"""Complete a write group on the decorated repository.
586
Smart methods peform operations in a single step so this api
587
is not really applicable except as a compatibility thunk
588
for older plugins that don't use e.g. the CommitBuilder
591
:param suppress_errors: see Repository.abort_write_group.
594
return self._real_repository.abort_write_group(
595
suppress_errors=suppress_errors)
597
def commit_write_group(self):
598
"""Complete a write group on the decorated repository.
600
Smart methods peform operations in a single step so this api
601
is not really applicable except as a compatibility thunk
602
for older plugins that don't use e.g. the CommitBuilder
606
return self._real_repository.commit_write_group()
608
def resume_write_group(self, tokens):
610
return self._real_repository.resume_write_group(tokens)
612
def suspend_write_group(self):
614
return self._real_repository.suspend_write_group()
616
def _ensure_real(self):
617
"""Ensure that there is a _real_repository set.
619
Used before calls to self._real_repository.
621
if self._real_repository is None:
622
self.bzrdir._ensure_real()
623
self._set_real_repository(
624
self.bzrdir._real_bzrdir.open_repository())
626
def _translate_error(self, err, **context):
627
self.bzrdir._translate_error(err, repository=self, **context)
629
def find_text_key_references(self):
630
"""Find the text key references within the repository.
632
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
633
revision_ids. Each altered file-ids has the exact revision_ids that
634
altered it listed explicitly.
635
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
636
to whether they were referred to by the inventory of the
637
revision_id that they contain. The inventory texts from all present
638
revision ids are assessed to generate this report.
641
return self._real_repository.find_text_key_references()
643
def _generate_text_key_index(self):
644
"""Generate a new text key index for the repository.
646
This is an expensive function that will take considerable time to run.
648
:return: A dict mapping (file_id, revision_id) tuples to a list of
649
parents, also (file_id, revision_id) tuples.
652
return self._real_repository._generate_text_key_index()
654
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
655
def get_revision_graph(self, revision_id=None):
656
"""See Repository.get_revision_graph()."""
657
return self._get_revision_graph(revision_id)
659
def _get_revision_graph(self, revision_id):
660
"""Private method for using with old (< 1.2) servers to fallback."""
661
if revision_id is None:
663
elif revision.is_null(revision_id):
666
path = self.bzrdir._path_for_remote_call(self._client)
667
response = self._call_expecting_body(
668
'Repository.get_revision_graph', path, revision_id)
669
response_tuple, response_handler = response
670
if response_tuple[0] != 'ok':
671
raise errors.UnexpectedSmartServerResponse(response_tuple)
672
coded = response_handler.read_body_bytes()
674
# no revisions in this repository!
676
lines = coded.split('\n')
679
d = tuple(line.split())
680
revision_graph[d[0]] = d[1:]
682
return revision_graph
685
"""See Repository._get_sink()."""
686
return RemoteStreamSink(self)
688
def _get_source(self, to_format):
689
"""Return a source for streaming from this repository."""
690
return RemoteStreamSource(self, to_format)
692
def has_revision(self, revision_id):
693
"""See Repository.has_revision()."""
694
if revision_id == NULL_REVISION:
695
# The null revision is always present.
697
path = self.bzrdir._path_for_remote_call(self._client)
698
response = self._call('Repository.has_revision', path, revision_id)
699
if response[0] not in ('yes', 'no'):
700
raise errors.UnexpectedSmartServerResponse(response)
701
if response[0] == 'yes':
703
for fallback_repo in self._fallback_repositories:
704
if fallback_repo.has_revision(revision_id):
708
def has_revisions(self, revision_ids):
709
"""See Repository.has_revisions()."""
710
# FIXME: This does many roundtrips, particularly when there are
711
# fallback repositories. -- mbp 20080905
713
for revision_id in revision_ids:
714
if self.has_revision(revision_id):
715
result.add(revision_id)
718
def has_same_location(self, other):
719
return (self.__class__ is other.__class__ and
720
self.bzrdir.transport.base == other.bzrdir.transport.base)
722
def get_graph(self, other_repository=None):
723
"""Return the graph for this repository format"""
724
parents_provider = self._make_parents_provider(other_repository)
725
return graph.Graph(parents_provider)
727
def gather_stats(self, revid=None, committers=None):
728
"""See Repository.gather_stats()."""
729
path = self.bzrdir._path_for_remote_call(self._client)
730
# revid can be None to indicate no revisions, not just NULL_REVISION
731
if revid is None or revision.is_null(revid):
735
if committers is None or not committers:
736
fmt_committers = 'no'
738
fmt_committers = 'yes'
739
response_tuple, response_handler = self._call_expecting_body(
740
'Repository.gather_stats', path, fmt_revid, fmt_committers)
741
if response_tuple[0] != 'ok':
742
raise errors.UnexpectedSmartServerResponse(response_tuple)
744
body = response_handler.read_body_bytes()
746
for line in body.split('\n'):
749
key, val_text = line.split(':')
750
if key in ('revisions', 'size', 'committers'):
751
result[key] = int(val_text)
752
elif key in ('firstrev', 'latestrev'):
753
values = val_text.split(' ')[1:]
754
result[key] = (float(values[0]), long(values[1]))
758
def find_branches(self, using=False):
759
"""See Repository.find_branches()."""
760
# should be an API call to the server.
762
return self._real_repository.find_branches(using=using)
764
def get_physical_lock_status(self):
765
"""See Repository.get_physical_lock_status()."""
766
# should be an API call to the server.
768
return self._real_repository.get_physical_lock_status()
770
def is_in_write_group(self):
771
"""Return True if there is an open write group.
773
write groups are only applicable locally for the smart server..
775
if self._real_repository:
776
return self._real_repository.is_in_write_group()
779
return self._lock_count >= 1
782
"""See Repository.is_shared()."""
783
path = self.bzrdir._path_for_remote_call(self._client)
784
response = self._call('Repository.is_shared', path)
785
if response[0] not in ('yes', 'no'):
786
raise SmartProtocolError('unexpected response code %s' % (response,))
787
return response[0] == 'yes'
789
def is_write_locked(self):
790
return self._lock_mode == 'w'
793
# wrong eventually - want a local lock cache context
794
if not self._lock_mode:
795
self._lock_mode = 'r'
797
self._unstacked_provider.enable_cache(cache_misses=False)
798
if self._real_repository is not None:
799
self._real_repository.lock_read()
801
self._lock_count += 1
803
def _remote_lock_write(self, token):
804
path = self.bzrdir._path_for_remote_call(self._client)
807
err_context = {'token': token}
808
response = self._call('Repository.lock_write', path, token,
810
if response[0] == 'ok':
814
raise errors.UnexpectedSmartServerResponse(response)
816
def lock_write(self, token=None, _skip_rpc=False):
817
if not self._lock_mode:
819
if self._lock_token is not None:
820
if token != self._lock_token:
821
raise errors.TokenMismatch(token, self._lock_token)
822
self._lock_token = token
824
self._lock_token = self._remote_lock_write(token)
825
# if self._lock_token is None, then this is something like packs or
826
# svn where we don't get to lock the repo, or a weave style repository
827
# where we cannot lock it over the wire and attempts to do so will
829
if self._real_repository is not None:
830
self._real_repository.lock_write(token=self._lock_token)
831
if token is not None:
832
self._leave_lock = True
834
self._leave_lock = False
835
self._lock_mode = 'w'
837
self._unstacked_provider.enable_cache(cache_misses=False)
838
elif self._lock_mode == 'r':
839
raise errors.ReadOnlyError(self)
841
self._lock_count += 1
842
return self._lock_token or None
844
def leave_lock_in_place(self):
845
if not self._lock_token:
846
raise NotImplementedError(self.leave_lock_in_place)
847
self._leave_lock = True
849
def dont_leave_lock_in_place(self):
850
if not self._lock_token:
851
raise NotImplementedError(self.dont_leave_lock_in_place)
852
self._leave_lock = False
854
def _set_real_repository(self, repository):
855
"""Set the _real_repository for this repository.
857
:param repository: The repository to fallback to for non-hpss
858
implemented operations.
860
if self._real_repository is not None:
861
# Replacing an already set real repository.
862
# We cannot do this [currently] if the repository is locked -
863
# synchronised state might be lost.
865
raise AssertionError('_real_repository is already set')
866
if isinstance(repository, RemoteRepository):
867
raise AssertionError()
868
self._real_repository = repository
869
# If the _real_repository has _fallback_repositories, clear them out,
870
# because we want it to have the same set as this repository. This is
871
# reasonable to do because the fallbacks we clear here are from a
872
# "real" branch, and we're about to replace them with the equivalents
873
# from a RemoteBranch.
874
self._real_repository._fallback_repositories = []
875
for fb in self._fallback_repositories:
876
self._real_repository.add_fallback_repository(fb)
877
if self._lock_mode == 'w':
878
# if we are already locked, the real repository must be able to
879
# acquire the lock with our token.
880
self._real_repository.lock_write(self._lock_token)
881
elif self._lock_mode == 'r':
882
self._real_repository.lock_read()
884
def start_write_group(self):
885
"""Start a write group on the decorated repository.
887
Smart methods peform operations in a single step so this api
888
is not really applicable except as a compatibility thunk
889
for older plugins that don't use e.g. the CommitBuilder
893
return self._real_repository.start_write_group()
895
def _unlock(self, token):
896
path = self.bzrdir._path_for_remote_call(self._client)
898
# with no token the remote repository is not persistently locked.
900
err_context = {'token': token}
901
response = self._call('Repository.unlock', path, token,
903
if response == ('ok',):
906
raise errors.UnexpectedSmartServerResponse(response)
909
if not self._lock_count:
910
raise errors.LockNotHeld(self)
911
self._lock_count -= 1
912
if self._lock_count > 0:
914
self._unstacked_provider.disable_cache()
915
old_mode = self._lock_mode
916
self._lock_mode = None
918
# The real repository is responsible at present for raising an
919
# exception if it's in an unfinished write group. However, it
920
# normally will *not* actually remove the lock from disk - that's
921
# done by the server on receiving the Repository.unlock call.
922
# This is just to let the _real_repository stay up to date.
923
if self._real_repository is not None:
924
self._real_repository.unlock()
926
# The rpc-level lock should be released even if there was a
927
# problem releasing the vfs-based lock.
929
# Only write-locked repositories need to make a remote method
930
# call to perfom the unlock.
931
old_token = self._lock_token
932
self._lock_token = None
933
if not self._leave_lock:
934
self._unlock(old_token)
936
def break_lock(self):
937
# should hand off to the network
939
return self._real_repository.break_lock()
941
def _get_tarball(self, compression):
942
"""Return a TemporaryFile containing a repository tarball.
944
Returns None if the server does not support sending tarballs.
947
path = self.bzrdir._path_for_remote_call(self._client)
949
response, protocol = self._call_expecting_body(
950
'Repository.tarball', path, compression)
951
except errors.UnknownSmartMethod:
952
protocol.cancel_read_body()
954
if response[0] == 'ok':
955
# Extract the tarball and return it
956
t = tempfile.NamedTemporaryFile()
957
# TODO: rpc layer should read directly into it...
958
t.write(protocol.read_body_bytes())
961
raise errors.UnexpectedSmartServerResponse(response)
963
def sprout(self, to_bzrdir, revision_id=None):
964
# TODO: Option to control what format is created?
966
dest_repo = self._real_repository._format.initialize(to_bzrdir,
968
dest_repo.fetch(self, revision_id=revision_id)
971
### These methods are just thin shims to the VFS object for now.
973
def revision_tree(self, revision_id):
975
return self._real_repository.revision_tree(revision_id)
977
def get_serializer_format(self):
979
return self._real_repository.get_serializer_format()
981
def get_commit_builder(self, branch, parents, config, timestamp=None,
982
timezone=None, committer=None, revprops=None,
984
# FIXME: It ought to be possible to call this without immediately
985
# triggering _ensure_real. For now it's the easiest thing to do.
987
real_repo = self._real_repository
988
builder = real_repo.get_commit_builder(branch, parents,
989
config, timestamp=timestamp, timezone=timezone,
990
committer=committer, revprops=revprops, revision_id=revision_id)
993
def add_fallback_repository(self, repository):
994
"""Add a repository to use for looking up data not held locally.
996
:param repository: A repository.
998
# XXX: At the moment the RemoteRepository will allow fallbacks
999
# unconditionally - however, a _real_repository will usually exist,
1000
# and may raise an error if it's not accommodated by the underlying
1001
# format. Eventually we should check when opening the repository
1002
# whether it's willing to allow them or not.
1004
# We need to accumulate additional repositories here, to pass them in
1007
self._fallback_repositories.append(repository)
1008
# If self._real_repository was parameterised already (e.g. because a
1009
# _real_branch had its get_stacked_on_url method called), then the
1010
# repository to be added may already be in the _real_repositories list.
1011
if self._real_repository is not None:
1012
if repository not in self._real_repository._fallback_repositories:
1013
self._real_repository.add_fallback_repository(repository)
1015
# They are also seen by the fallback repository. If it doesn't
1016
# exist yet they'll be added then. This implicitly copies them.
1019
def add_inventory(self, revid, inv, parents):
1021
return self._real_repository.add_inventory(revid, inv, parents)
1023
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1026
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1027
delta, new_revision_id, parents)
1029
def add_revision(self, rev_id, rev, inv=None, config=None):
1031
return self._real_repository.add_revision(
1032
rev_id, rev, inv=inv, config=config)
1035
def get_inventory(self, revision_id):
1037
return self._real_repository.get_inventory(revision_id)
1039
def iter_inventories(self, revision_ids):
1041
return self._real_repository.iter_inventories(revision_ids)
1044
def get_revision(self, revision_id):
1046
return self._real_repository.get_revision(revision_id)
1048
def get_transaction(self):
1050
return self._real_repository.get_transaction()
1053
def clone(self, a_bzrdir, revision_id=None):
1055
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1057
def make_working_trees(self):
1058
"""See Repository.make_working_trees"""
1060
return self._real_repository.make_working_trees()
1062
def revision_ids_to_search_result(self, result_set):
1063
"""Convert a set of revision ids to a graph SearchResult."""
1064
result_parents = set()
1065
for parents in self.get_graph().get_parent_map(
1066
result_set).itervalues():
1067
result_parents.update(parents)
1068
included_keys = result_set.intersection(result_parents)
1069
start_keys = result_set.difference(included_keys)
1070
exclude_keys = result_parents.difference(result_set)
1071
result = graph.SearchResult(start_keys, exclude_keys,
1072
len(result_set), result_set)
1076
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1077
"""Return the revision ids that other has that this does not.
1079
These are returned in topological order.
1081
revision_id: only return revision ids included by revision_id.
1083
return repository.InterRepository.get(
1084
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1086
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1088
if fetch_spec is not None and revision_id is not None:
1089
raise AssertionError(
1090
"fetch_spec and revision_id are mutually exclusive.")
1091
# Not delegated to _real_repository so that InterRepository.get has a
1092
# chance to find an InterRepository specialised for RemoteRepository.
1093
if self.has_same_location(source) and fetch_spec is None:
1094
# check that last_revision is in 'from' and then return a
1096
if (revision_id is not None and
1097
not revision.is_null(revision_id)):
1098
self.get_revision(revision_id)
1100
inter = repository.InterRepository.get(source, self)
1102
return inter.fetch(revision_id=revision_id, pb=pb,
1103
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1104
except NotImplementedError:
1105
raise errors.IncompatibleRepositories(source, self)
1107
def create_bundle(self, target, base, fileobj, format=None):
1109
self._real_repository.create_bundle(target, base, fileobj, format)
1112
def get_ancestry(self, revision_id, topo_sorted=True):
1114
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1116
def fileids_altered_by_revision_ids(self, revision_ids):
1118
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1120
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1122
return self._real_repository._get_versioned_file_checker(
1123
revisions, revision_versions_cache)
1125
def iter_files_bytes(self, desired_files):
1126
"""See Repository.iter_file_bytes.
1129
return self._real_repository.iter_files_bytes(desired_files)
1131
def get_parent_map(self, revision_ids):
1132
"""See bzrlib.Graph.get_parent_map()."""
1133
return self._make_parents_provider().get_parent_map(revision_ids)
1135
def _get_parent_map_rpc(self, keys):
1136
"""Helper for get_parent_map that performs the RPC."""
1137
medium = self._client._medium
1138
if medium._is_remote_before((1, 2)):
1139
# We already found out that the server can't understand
1140
# Repository.get_parent_map requests, so just fetch the whole
1142
# XXX: Note that this will issue a deprecation warning. This is ok
1143
# :- its because we're working with a deprecated server anyway, and
1144
# the user will almost certainly have seen a warning about the
1145
# server version already.
1146
rg = self.get_revision_graph()
1147
# There is an api discrepency between get_parent_map and
1148
# get_revision_graph. Specifically, a "key:()" pair in
1149
# get_revision_graph just means a node has no parents. For
1150
# "get_parent_map" it means the node is a ghost. So fix up the
1151
# graph to correct this.
1152
# https://bugs.launchpad.net/bzr/+bug/214894
1153
# There is one other "bug" which is that ghosts in
1154
# get_revision_graph() are not returned at all. But we won't worry
1155
# about that for now.
1156
for node_id, parent_ids in rg.iteritems():
1157
if parent_ids == ():
1158
rg[node_id] = (NULL_REVISION,)
1159
rg[NULL_REVISION] = ()
1164
raise ValueError('get_parent_map(None) is not valid')
1165
if NULL_REVISION in keys:
1166
keys.discard(NULL_REVISION)
1167
found_parents = {NULL_REVISION:()}
1169
return found_parents
1172
# TODO(Needs analysis): We could assume that the keys being requested
1173
# from get_parent_map are in a breadth first search, so typically they
1174
# will all be depth N from some common parent, and we don't have to
1175
# have the server iterate from the root parent, but rather from the
1176
# keys we're searching; and just tell the server the keyspace we
1177
# already have; but this may be more traffic again.
1179
# Transform self._parents_map into a search request recipe.
1180
# TODO: Manage this incrementally to avoid covering the same path
1181
# repeatedly. (The server will have to on each request, but the less
1182
# work done the better).
1183
parents_map = self._unstacked_provider.get_cached_map()
1184
if parents_map is None:
1185
# Repository is not locked, so there's no cache.
1187
start_set = set(parents_map)
1188
result_parents = set()
1189
for parents in parents_map.itervalues():
1190
result_parents.update(parents)
1191
stop_keys = result_parents.difference(start_set)
1192
included_keys = start_set.intersection(result_parents)
1193
start_set.difference_update(included_keys)
1194
recipe = (start_set, stop_keys, len(parents_map))
1195
body = self._serialise_search_recipe(recipe)
1196
path = self.bzrdir._path_for_remote_call(self._client)
1198
if type(key) is not str:
1200
"key %r not a plain string" % (key,))
1201
verb = 'Repository.get_parent_map'
1202
args = (path,) + tuple(keys)
1204
response = self._call_with_body_bytes_expecting_body(
1206
except errors.UnknownSmartMethod:
1207
# Server does not support this method, so get the whole graph.
1208
# Worse, we have to force a disconnection, because the server now
1209
# doesn't realise it has a body on the wire to consume, so the
1210
# only way to recover is to abandon the connection.
1212
'Server is too old for fast get_parent_map, reconnecting. '
1213
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1215
# To avoid having to disconnect repeatedly, we keep track of the
1216
# fact the server doesn't understand remote methods added in 1.2.
1217
medium._remember_remote_is_before((1, 2))
1218
return self.get_revision_graph(None)
1219
response_tuple, response_handler = response
1220
if response_tuple[0] not in ['ok']:
1221
response_handler.cancel_read_body()
1222
raise errors.UnexpectedSmartServerResponse(response_tuple)
1223
if response_tuple[0] == 'ok':
1224
coded = bz2.decompress(response_handler.read_body_bytes())
1226
# no revisions found
1228
lines = coded.split('\n')
1231
d = tuple(line.split())
1233
revision_graph[d[0]] = d[1:]
1235
# No parents - so give the Graph result (NULL_REVISION,).
1236
revision_graph[d[0]] = (NULL_REVISION,)
1237
return revision_graph
1240
def get_signature_text(self, revision_id):
1242
return self._real_repository.get_signature_text(revision_id)
1245
@symbol_versioning.deprecated_method(symbol_versioning.one_three)
1246
def get_revision_graph_with_ghosts(self, revision_ids=None):
1248
return self._real_repository.get_revision_graph_with_ghosts(
1249
revision_ids=revision_ids)
1252
def get_inventory_xml(self, revision_id):
1254
return self._real_repository.get_inventory_xml(revision_id)
1256
def deserialise_inventory(self, revision_id, xml):
1258
return self._real_repository.deserialise_inventory(revision_id, xml)
1260
def reconcile(self, other=None, thorough=False):
1262
return self._real_repository.reconcile(other=other, thorough=thorough)
1264
def all_revision_ids(self):
1266
return self._real_repository.all_revision_ids()
1269
def get_deltas_for_revisions(self, revisions):
1271
return self._real_repository.get_deltas_for_revisions(revisions)
1274
def get_revision_delta(self, revision_id):
1276
return self._real_repository.get_revision_delta(revision_id)
1279
def revision_trees(self, revision_ids):
1281
return self._real_repository.revision_trees(revision_ids)
1284
def get_revision_reconcile(self, revision_id):
1286
return self._real_repository.get_revision_reconcile(revision_id)
1289
def check(self, revision_ids=None):
1291
return self._real_repository.check(revision_ids=revision_ids)
1293
def copy_content_into(self, destination, revision_id=None):
1295
return self._real_repository.copy_content_into(
1296
destination, revision_id=revision_id)
1298
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1299
# get a tarball of the remote repository, and copy from that into the
1301
from bzrlib import osutils
1303
# TODO: Maybe a progress bar while streaming the tarball?
1304
note("Copying repository content as tarball...")
1305
tar_file = self._get_tarball('bz2')
1306
if tar_file is None:
1308
destination = to_bzrdir.create_repository()
1310
tar = tarfile.open('repository', fileobj=tar_file,
1312
tmpdir = osutils.mkdtemp()
1314
_extract_tar(tar, tmpdir)
1315
tmp_bzrdir = BzrDir.open(tmpdir)
1316
tmp_repo = tmp_bzrdir.open_repository()
1317
tmp_repo.copy_content_into(destination, revision_id)
1319
osutils.rmtree(tmpdir)
1323
# TODO: Suggestion from john: using external tar is much faster than
1324
# python's tarfile library, but it may not work on windows.
1327
def inventories(self):
1328
"""Decorate the real repository for now.
1330
In the long term a full blown network facility is needed to
1331
avoid creating a real repository object locally.
1334
return self._real_repository.inventories
1338
"""Compress the data within the repository.
1340
This is not currently implemented within the smart server.
1343
return self._real_repository.pack()
1346
def revisions(self):
1347
"""Decorate the real repository for now.
1349
In the short term this should become a real object to intercept graph
1352
In the long term a full blown network facility is needed.
1355
return self._real_repository.revisions
1357
def set_make_working_trees(self, new_value):
1359
new_value_str = "True"
1361
new_value_str = "False"
1362
path = self.bzrdir._path_for_remote_call(self._client)
1364
response = self._call(
1365
'Repository.set_make_working_trees', path, new_value_str)
1366
except errors.UnknownSmartMethod:
1368
self._real_repository.set_make_working_trees(new_value)
1370
if response[0] != 'ok':
1371
raise errors.UnexpectedSmartServerResponse(response)
1374
def signatures(self):
1375
"""Decorate the real repository for now.
1377
In the long term a full blown network facility is needed to avoid
1378
creating a real repository object locally.
1381
return self._real_repository.signatures
1384
def sign_revision(self, revision_id, gpg_strategy):
1386
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1390
"""Decorate the real repository for now.
1392
In the long term a full blown network facility is needed to avoid
1393
creating a real repository object locally.
1396
return self._real_repository.texts
1399
def get_revisions(self, revision_ids):
1401
return self._real_repository.get_revisions(revision_ids)
1403
def supports_rich_root(self):
1404
return self._format.rich_root_data
1406
def iter_reverse_revision_history(self, revision_id):
1408
return self._real_repository.iter_reverse_revision_history(revision_id)
1411
def _serializer(self):
1412
return self._format._serializer
1414
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1416
return self._real_repository.store_revision_signature(
1417
gpg_strategy, plaintext, revision_id)
1419
def add_signature_text(self, revision_id, signature):
1421
return self._real_repository.add_signature_text(revision_id, signature)
1423
def has_signature_for_revision_id(self, revision_id):
1425
return self._real_repository.has_signature_for_revision_id(revision_id)
1427
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1429
return self._real_repository.item_keys_introduced_by(revision_ids,
1430
_files_pb=_files_pb)
1432
def revision_graph_can_have_wrong_parents(self):
1433
# The answer depends on the remote repo format.
1435
return self._real_repository.revision_graph_can_have_wrong_parents()
1437
def _find_inconsistent_revision_parents(self):
1439
return self._real_repository._find_inconsistent_revision_parents()
1441
def _check_for_inconsistent_revision_parents(self):
1443
return self._real_repository._check_for_inconsistent_revision_parents()
1445
def _make_parents_provider(self, other=None):
1446
providers = [self._unstacked_provider]
1447
if other is not None:
1448
providers.insert(0, other)
1449
providers.extend(r._make_parents_provider() for r in
1450
self._fallback_repositories)
1451
return graph._StackedParentsProvider(providers)
1453
def _serialise_search_recipe(self, recipe):
1454
"""Serialise a graph search recipe.
1456
:param recipe: A search recipe (start, stop, count).
1457
:return: Serialised bytes.
1459
start_keys = ' '.join(recipe[0])
1460
stop_keys = ' '.join(recipe[1])
1461
count = str(recipe[2])
1462
return '\n'.join((start_keys, stop_keys, count))
1464
def _serialise_search_result(self, search_result):
1465
if isinstance(search_result, graph.PendingAncestryResult):
1466
parts = ['ancestry-of']
1467
parts.extend(search_result.heads)
1469
recipe = search_result.get_recipe()
1470
parts = ['search', self._serialise_search_recipe(recipe)]
1471
return '\n'.join(parts)
1474
path = self.bzrdir._path_for_remote_call(self._client)
1476
response = self._call('PackRepository.autopack', path)
1477
except errors.UnknownSmartMethod:
1479
self._real_repository._pack_collection.autopack()
1481
if self._real_repository is not None:
1482
# Reset the real repository's cache of pack names.
1483
# XXX: At some point we may be able to skip this and just rely on
1484
# the automatic retry logic to do the right thing, but for now we
1485
# err on the side of being correct rather than being optimal.
1486
self._real_repository._pack_collection.reload_pack_names()
1487
if response[0] != 'ok':
1488
raise errors.UnexpectedSmartServerResponse(response)
1491
class RemoteStreamSink(repository.StreamSink):
1493
def _insert_real(self, stream, src_format, resume_tokens):
1494
self.target_repo._ensure_real()
1495
sink = self.target_repo._real_repository._get_sink()
1496
result = sink.insert_stream(stream, src_format, resume_tokens)
1498
self.target_repo.autopack()
1501
def insert_stream(self, stream, src_format, resume_tokens):
1502
repo = self.target_repo
1503
client = repo._client
1504
medium = client._medium
1505
if medium._is_remote_before((1, 13)):
1506
# No possible way this can work.
1507
return self._insert_real(stream, src_format, resume_tokens)
1508
path = repo.bzrdir._path_for_remote_call(client)
1509
if not resume_tokens:
1510
# XXX: Ugly but important for correctness, *will* be fixed during
1511
# 1.13 cycle. Pushing a stream that is interrupted results in a
1512
# fallback to the _real_repositories sink *with a partial stream*.
1513
# Thats bad because we insert less data than bzr expected. To avoid
1514
# this we do a trial push to make sure the verb is accessible, and
1515
# do not fallback when actually pushing the stream. A cleanup patch
1516
# is going to look at rewinding/restarting the stream/partial
1518
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1520
response = client.call_with_body_stream(
1521
('Repository.insert_stream', path, ''), byte_stream)
1522
except errors.UnknownSmartMethod:
1523
medium._remember_remote_is_before((1,13))
1524
return self._insert_real(stream, src_format, resume_tokens)
1525
byte_stream = smart_repo._stream_to_byte_stream(
1527
resume_tokens = ' '.join(resume_tokens)
1528
response = client.call_with_body_stream(
1529
('Repository.insert_stream', path, resume_tokens), byte_stream)
1530
if response[0][0] not in ('ok', 'missing-basis'):
1531
raise errors.UnexpectedSmartServerResponse(response)
1532
if response[0][0] == 'missing-basis':
1533
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1534
resume_tokens = tokens
1535
return resume_tokens, missing_keys
1537
if self.target_repo._real_repository is not None:
1538
collection = getattr(self.target_repo._real_repository,
1539
'_pack_collection', None)
1540
if collection is not None:
1541
collection.reload_pack_names()
1545
class RemoteStreamSource(repository.StreamSource):
1546
"""Stream data from a remote server."""
1548
def get_stream(self, search):
1549
# streaming with fallback repositories is not well defined yet: The
1550
# remote repository cannot see the fallback repositories, and thus
1551
# cannot satisfy the entire search in the general case. Likewise the
1552
# fallback repositories cannot reify the search to determine what they
1553
# should send. It likely needs a return value in the stream listing the
1554
# edge of the search to resume from in fallback repositories.
1555
if self.from_repository._fallback_repositories:
1556
return repository.StreamSource.get_stream(self, search)
1557
repo = self.from_repository
1558
client = repo._client
1559
medium = client._medium
1560
if medium._is_remote_before((1, 13)):
1561
# No possible way this can work.
1562
return repository.StreamSource.get_stream(self, search)
1563
path = repo.bzrdir._path_for_remote_call(client)
1565
search_bytes = repo._serialise_search_result(search)
1566
response = repo._call_with_body_bytes_expecting_body(
1567
'Repository.get_stream',
1568
(path, self.to_format.network_name()), search_bytes)
1569
response_tuple, response_handler = response
1570
except errors.UnknownSmartMethod:
1571
medium._remember_remote_is_before((1,13))
1572
return repository.StreamSource.get_stream(self, search)
1573
if response_tuple[0] != 'ok':
1574
raise errors.UnexpectedSmartServerResponse(response_tuple)
1575
byte_stream = response_handler.read_streamed_body()
1576
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1577
if src_format.network_name() != repo._format.network_name():
1578
raise AssertionError(
1579
"Mismatched RemoteRepository and stream src %r, %r" % (
1580
src_format.network_name(), repo._format.network_name()))
1584
class RemoteBranchLockableFiles(LockableFiles):
1585
"""A 'LockableFiles' implementation that talks to a smart server.
1587
This is not a public interface class.
1590
def __init__(self, bzrdir, _client):
1591
self.bzrdir = bzrdir
1592
self._client = _client
1593
self._need_find_modes = True
1594
LockableFiles.__init__(
1595
self, bzrdir.get_branch_transport(None),
1596
'lock', lockdir.LockDir)
1598
def _find_modes(self):
1599
# RemoteBranches don't let the client set the mode of control files.
1600
self._dir_mode = None
1601
self._file_mode = None
1604
class RemoteBranchFormat(branch.BranchFormat):
1606
def __init__(self, network_name=None):
1607
super(RemoteBranchFormat, self).__init__()
1608
self._matchingbzrdir = RemoteBzrDirFormat()
1609
self._matchingbzrdir.set_branch_format(self)
1610
self._custom_format = None
1611
self._network_name = network_name
1613
def __eq__(self, other):
1614
return (isinstance(other, RemoteBranchFormat) and
1615
self.__dict__ == other.__dict__)
1617
def _ensure_real(self):
1618
if self._custom_format is None:
1619
self._custom_format = branch.network_format_registry.get(
1622
def get_format_description(self):
1623
return 'Remote BZR Branch'
1625
def network_name(self):
1626
return self._network_name
1628
def open(self, a_bzrdir):
1629
return a_bzrdir.open_branch()
1631
def _vfs_initialize(self, a_bzrdir):
1632
# Initialisation when using a local bzrdir object, or a non-vfs init
1633
# method is not available on the server.
1634
# self._custom_format is always set - the start of initialize ensures
1636
if isinstance(a_bzrdir, RemoteBzrDir):
1637
a_bzrdir._ensure_real()
1638
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1640
# We assume the bzrdir is parameterised; it may not be.
1641
result = self._custom_format.initialize(a_bzrdir)
1642
if (isinstance(a_bzrdir, RemoteBzrDir) and
1643
not isinstance(result, RemoteBranch)):
1644
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1647
def initialize(self, a_bzrdir):
1648
# 1) get the network name to use.
1649
if self._custom_format:
1650
network_name = self._custom_format.network_name()
1652
# Select the current bzrlib default and ask for that.
1653
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1654
reference_format = reference_bzrdir_format.get_branch_format()
1655
self._custom_format = reference_format
1656
network_name = reference_format.network_name()
1657
# Being asked to create on a non RemoteBzrDir:
1658
if not isinstance(a_bzrdir, RemoteBzrDir):
1659
return self._vfs_initialize(a_bzrdir)
1660
medium = a_bzrdir._client._medium
1661
if medium._is_remote_before((1, 13)):
1662
return self._vfs_initialize(a_bzrdir)
1663
# Creating on a remote bzr dir.
1664
# 2) try direct creation via RPC
1665
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1666
verb = 'BzrDir.create_branch'
1668
response = a_bzrdir._call(verb, path, network_name)
1669
except errors.UnknownSmartMethod:
1670
# Fallback - use vfs methods
1671
medium._remember_remote_is_before((1, 13))
1672
return self._vfs_initialize(a_bzrdir)
1673
if response[0] != 'ok':
1674
raise errors.UnexpectedSmartServerResponse(response)
1675
# Turn the response into a RemoteRepository object.
1676
format = RemoteBranchFormat(network_name=response[1])
1677
repo_format = response_tuple_to_repo_format(response[3:])
1678
if response[2] == '':
1679
repo_bzrdir = a_bzrdir
1681
repo_bzrdir = RemoteBzrDir(
1682
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1684
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1685
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1686
format=format, setup_stacking=False)
1687
# XXX: We know this is a new branch, so it must have revno 0, revid
1688
# NULL_REVISION. Creating the branch locked would make this be unable
1689
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1690
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1691
return remote_branch
1693
def make_tags(self, branch):
1695
return self._custom_format.make_tags(branch)
1697
def supports_tags(self):
1698
# Remote branches might support tags, but we won't know until we
1699
# access the real remote branch.
1701
return self._custom_format.supports_tags()
1704
class RemoteBranch(branch.Branch, _RpcHelper):
1705
"""Branch stored on a server accessed by HPSS RPC.
1707
At the moment most operations are mapped down to simple file operations.
1710
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1711
_client=None, format=None, setup_stacking=True):
1712
"""Create a RemoteBranch instance.
1714
:param real_branch: An optional local implementation of the branch
1715
format, usually accessing the data via the VFS.
1716
:param _client: Private parameter for testing.
1717
:param format: A RemoteBranchFormat object, None to create one
1718
automatically. If supplied it should have a network_name already
1720
:param setup_stacking: If True make an RPC call to determine the
1721
stacked (or not) status of the branch. If False assume the branch
1724
# We intentionally don't call the parent class's __init__, because it
1725
# will try to assign to self.tags, which is a property in this subclass.
1726
# And the parent's __init__ doesn't do much anyway.
1727
self._revision_id_to_revno_cache = None
1728
self._partial_revision_id_to_revno_cache = {}
1729
self._revision_history_cache = None
1730
self._last_revision_info_cache = None
1731
self._merge_sorted_revisions_cache = None
1732
self.bzrdir = remote_bzrdir
1733
if _client is not None:
1734
self._client = _client
1736
self._client = remote_bzrdir._client
1737
self.repository = remote_repository
1738
if real_branch is not None:
1739
self._real_branch = real_branch
1740
# Give the remote repository the matching real repo.
1741
real_repo = self._real_branch.repository
1742
if isinstance(real_repo, RemoteRepository):
1743
real_repo._ensure_real()
1744
real_repo = real_repo._real_repository
1745
self.repository._set_real_repository(real_repo)
1746
# Give the branch the remote repository to let fast-pathing happen.
1747
self._real_branch.repository = self.repository
1749
self._real_branch = None
1750
# Fill out expected attributes of branch for bzrlib api users.
1751
self.base = self.bzrdir.root_transport.base
1752
self._control_files = None
1753
self._lock_mode = None
1754
self._lock_token = None
1755
self._repo_lock_token = None
1756
self._lock_count = 0
1757
self._leave_lock = False
1758
# Setup a format: note that we cannot call _ensure_real until all the
1759
# attributes above are set: This code cannot be moved higher up in this
1762
self._format = RemoteBranchFormat()
1763
if real_branch is not None:
1764
self._format._network_name = \
1765
self._real_branch._format.network_name()
1767
self._format = format
1768
if not self._format._network_name:
1769
# Did not get from open_branchV2 - old server.
1771
self._format._network_name = \
1772
self._real_branch._format.network_name()
1773
self.tags = self._format.make_tags(self)
1774
# The base class init is not called, so we duplicate this:
1775
hooks = branch.Branch.hooks['open']
1779
self._setup_stacking()
1781
def _setup_stacking(self):
1782
# configure stacking into the remote repository, by reading it from
1785
fallback_url = self.get_stacked_on_url()
1786
except (errors.NotStacked, errors.UnstackableBranchFormat,
1787
errors.UnstackableRepositoryFormat), e:
1789
# it's relative to this branch...
1790
fallback_url = urlutils.join(self.base, fallback_url)
1791
transports = [self.bzrdir.root_transport]
1792
stacked_on = branch.Branch.open(fallback_url,
1793
possible_transports=transports)
1794
self.repository.add_fallback_repository(stacked_on.repository)
1796
def _get_real_transport(self):
1797
# if we try vfs access, return the real branch's vfs transport
1799
return self._real_branch._transport
1801
_transport = property(_get_real_transport)
1804
return "%s(%s)" % (self.__class__.__name__, self.base)
1808
def _ensure_real(self):
1809
"""Ensure that there is a _real_branch set.
1811
Used before calls to self._real_branch.
1813
if self._real_branch is None:
1814
if not vfs.vfs_enabled():
1815
raise AssertionError('smart server vfs must be enabled '
1816
'to use vfs implementation')
1817
self.bzrdir._ensure_real()
1818
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1819
if self.repository._real_repository is None:
1820
# Give the remote repository the matching real repo.
1821
real_repo = self._real_branch.repository
1822
if isinstance(real_repo, RemoteRepository):
1823
real_repo._ensure_real()
1824
real_repo = real_repo._real_repository
1825
self.repository._set_real_repository(real_repo)
1826
# Give the real branch the remote repository to let fast-pathing
1828
self._real_branch.repository = self.repository
1829
if self._lock_mode == 'r':
1830
self._real_branch.lock_read()
1831
elif self._lock_mode == 'w':
1832
self._real_branch.lock_write(token=self._lock_token)
1834
def _translate_error(self, err, **context):
1835
self.repository._translate_error(err, branch=self, **context)
1837
def _clear_cached_state(self):
1838
super(RemoteBranch, self)._clear_cached_state()
1839
if self._real_branch is not None:
1840
self._real_branch._clear_cached_state()
1842
def _clear_cached_state_of_remote_branch_only(self):
1843
"""Like _clear_cached_state, but doesn't clear the cache of
1846
This is useful when falling back to calling a method of
1847
self._real_branch that changes state. In that case the underlying
1848
branch changes, so we need to invalidate this RemoteBranch's cache of
1849
it. However, there's no need to invalidate the _real_branch's cache
1850
too, in fact doing so might harm performance.
1852
super(RemoteBranch, self)._clear_cached_state()
1855
def control_files(self):
1856
# Defer actually creating RemoteBranchLockableFiles until its needed,
1857
# because it triggers an _ensure_real that we otherwise might not need.
1858
if self._control_files is None:
1859
self._control_files = RemoteBranchLockableFiles(
1860
self.bzrdir, self._client)
1861
return self._control_files
1863
def _get_checkout_format(self):
1865
return self._real_branch._get_checkout_format()
1867
def get_physical_lock_status(self):
1868
"""See Branch.get_physical_lock_status()."""
1869
# should be an API call to the server, as branches must be lockable.
1871
return self._real_branch.get_physical_lock_status()
1873
def get_stacked_on_url(self):
1874
"""Get the URL this branch is stacked against.
1876
:raises NotStacked: If the branch is not stacked.
1877
:raises UnstackableBranchFormat: If the branch does not support
1879
:raises UnstackableRepositoryFormat: If the repository does not support
1883
# there may not be a repository yet, so we can't use
1884
# self._translate_error, so we can't use self._call either.
1885
response = self._client.call('Branch.get_stacked_on_url',
1886
self._remote_path())
1887
except errors.ErrorFromSmartServer, err:
1888
# there may not be a repository yet, so we can't call through
1889
# its _translate_error
1890
_translate_error(err, branch=self)
1891
except errors.UnknownSmartMethod, err:
1893
return self._real_branch.get_stacked_on_url()
1894
if response[0] != 'ok':
1895
raise errors.UnexpectedSmartServerResponse(response)
1898
def _vfs_get_tags_bytes(self):
1900
return self._real_branch._get_tags_bytes()
1902
def _get_tags_bytes(self):
1903
medium = self._client._medium
1904
if medium._is_remote_before((1, 13)):
1905
return self._vfs_get_tags_bytes()
1907
response = self._call('Branch.get_tags_bytes', self._remote_path())
1908
except errors.UnknownSmartMethod:
1909
medium._remember_remote_is_before((1, 13))
1910
return self._vfs_get_tags_bytes()
1913
def lock_read(self):
1914
self.repository.lock_read()
1915
if not self._lock_mode:
1916
self._lock_mode = 'r'
1917
self._lock_count = 1
1918
if self._real_branch is not None:
1919
self._real_branch.lock_read()
1921
self._lock_count += 1
1923
def _remote_lock_write(self, token):
1925
branch_token = repo_token = ''
1927
branch_token = token
1928
repo_token = self.repository.lock_write()
1929
self.repository.unlock()
1930
err_context = {'token': token}
1931
response = self._call(
1932
'Branch.lock_write', self._remote_path(), branch_token,
1933
repo_token or '', **err_context)
1934
if response[0] != 'ok':
1935
raise errors.UnexpectedSmartServerResponse(response)
1936
ok, branch_token, repo_token = response
1937
return branch_token, repo_token
1939
def lock_write(self, token=None):
1940
if not self._lock_mode:
1941
# Lock the branch and repo in one remote call.
1942
remote_tokens = self._remote_lock_write(token)
1943
self._lock_token, self._repo_lock_token = remote_tokens
1944
if not self._lock_token:
1945
raise SmartProtocolError('Remote server did not return a token!')
1946
# Tell the self.repository object that it is locked.
1947
self.repository.lock_write(
1948
self._repo_lock_token, _skip_rpc=True)
1950
if self._real_branch is not None:
1951
self._real_branch.lock_write(token=self._lock_token)
1952
if token is not None:
1953
self._leave_lock = True
1955
self._leave_lock = False
1956
self._lock_mode = 'w'
1957
self._lock_count = 1
1958
elif self._lock_mode == 'r':
1959
raise errors.ReadOnlyTransaction
1961
if token is not None:
1962
# A token was given to lock_write, and we're relocking, so
1963
# check that the given token actually matches the one we
1965
if token != self._lock_token:
1966
raise errors.TokenMismatch(token, self._lock_token)
1967
self._lock_count += 1
1968
# Re-lock the repository too.
1969
self.repository.lock_write(self._repo_lock_token)
1970
return self._lock_token or None
1972
def _set_tags_bytes(self, bytes):
1974
return self._real_branch._set_tags_bytes(bytes)
1976
def _unlock(self, branch_token, repo_token):
1977
err_context = {'token': str((branch_token, repo_token))}
1978
response = self._call(
1979
'Branch.unlock', self._remote_path(), branch_token,
1980
repo_token or '', **err_context)
1981
if response == ('ok',):
1983
raise errors.UnexpectedSmartServerResponse(response)
1987
self._lock_count -= 1
1988
if not self._lock_count:
1989
self._clear_cached_state()
1990
mode = self._lock_mode
1991
self._lock_mode = None
1992
if self._real_branch is not None:
1993
if (not self._leave_lock and mode == 'w' and
1994
self._repo_lock_token):
1995
# If this RemoteBranch will remove the physical lock
1996
# for the repository, make sure the _real_branch
1997
# doesn't do it first. (Because the _real_branch's
1998
# repository is set to be the RemoteRepository.)
1999
self._real_branch.repository.leave_lock_in_place()
2000
self._real_branch.unlock()
2002
# Only write-locked branched need to make a remote method
2003
# call to perfom the unlock.
2005
if not self._lock_token:
2006
raise AssertionError('Locked, but no token!')
2007
branch_token = self._lock_token
2008
repo_token = self._repo_lock_token
2009
self._lock_token = None
2010
self._repo_lock_token = None
2011
if not self._leave_lock:
2012
self._unlock(branch_token, repo_token)
2014
self.repository.unlock()
2016
def break_lock(self):
2018
return self._real_branch.break_lock()
2020
def leave_lock_in_place(self):
2021
if not self._lock_token:
2022
raise NotImplementedError(self.leave_lock_in_place)
2023
self._leave_lock = True
2025
def dont_leave_lock_in_place(self):
2026
if not self._lock_token:
2027
raise NotImplementedError(self.dont_leave_lock_in_place)
2028
self._leave_lock = False
2030
def _last_revision_info(self):
2031
response = self._call('Branch.last_revision_info', self._remote_path())
2032
if response[0] != 'ok':
2033
raise SmartProtocolError('unexpected response code %s' % (response,))
2034
revno = int(response[1])
2035
last_revision = response[2]
2036
return (revno, last_revision)
2038
def _gen_revision_history(self):
2039
"""See Branch._gen_revision_history()."""
2040
response_tuple, response_handler = self._call_expecting_body(
2041
'Branch.revision_history', self._remote_path())
2042
if response_tuple[0] != 'ok':
2043
raise errors.UnexpectedSmartServerResponse(response_tuple)
2044
result = response_handler.read_body_bytes().split('\x00')
2049
def _remote_path(self):
2050
return self.bzrdir._path_for_remote_call(self._client)
2052
def _set_last_revision_descendant(self, revision_id, other_branch,
2053
allow_diverged=False, allow_overwrite_descendant=False):
2054
# This performs additional work to meet the hook contract; while its
2055
# undesirable, we have to synthesise the revno to call the hook, and
2056
# not calling the hook is worse as it means changes can't be prevented.
2057
# Having calculated this though, we can't just call into
2058
# set_last_revision_info as a simple call, because there is a set_rh
2059
# hook that some folk may still be using.
2060
old_revno, old_revid = self.last_revision_info()
2061
history = self._lefthand_history(revision_id)
2062
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2063
err_context = {'other_branch': other_branch}
2064
response = self._call('Branch.set_last_revision_ex',
2065
self._remote_path(), self._lock_token, self._repo_lock_token,
2066
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2068
self._clear_cached_state()
2069
if len(response) != 3 and response[0] != 'ok':
2070
raise errors.UnexpectedSmartServerResponse(response)
2071
new_revno, new_revision_id = response[1:]
2072
self._last_revision_info_cache = new_revno, new_revision_id
2073
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2074
if self._real_branch is not None:
2075
cache = new_revno, new_revision_id
2076
self._real_branch._last_revision_info_cache = cache
2078
def _set_last_revision(self, revision_id):
2079
old_revno, old_revid = self.last_revision_info()
2080
# This performs additional work to meet the hook contract; while its
2081
# undesirable, we have to synthesise the revno to call the hook, and
2082
# not calling the hook is worse as it means changes can't be prevented.
2083
# Having calculated this though, we can't just call into
2084
# set_last_revision_info as a simple call, because there is a set_rh
2085
# hook that some folk may still be using.
2086
history = self._lefthand_history(revision_id)
2087
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2088
self._clear_cached_state()
2089
response = self._call('Branch.set_last_revision',
2090
self._remote_path(), self._lock_token, self._repo_lock_token,
2092
if response != ('ok',):
2093
raise errors.UnexpectedSmartServerResponse(response)
2094
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2097
def set_revision_history(self, rev_history):
2098
# Send just the tip revision of the history; the server will generate
2099
# the full history from that. If the revision doesn't exist in this
2100
# branch, NoSuchRevision will be raised.
2101
if rev_history == []:
2104
rev_id = rev_history[-1]
2105
self._set_last_revision(rev_id)
2106
for hook in branch.Branch.hooks['set_rh']:
2107
hook(self, rev_history)
2108
self._cache_revision_history(rev_history)
2110
def _get_parent_location(self):
2111
medium = self._client._medium
2112
if medium._is_remote_before((1, 13)):
2113
return self._vfs_get_parent_location()
2115
response = self._call('Branch.get_parent', self._remote_path())
2116
except errors.UnknownSmartMethod:
2117
medium._remember_remote_is_before((1, 13))
2118
return self._vfs_get_parent_location()
2119
if len(response) != 1:
2120
raise errors.UnexpectedSmartServerResponse(response)
2121
parent_location = response[0]
2122
if parent_location == '':
2124
return parent_location
2126
def _vfs_get_parent_location(self):
2128
return self._real_branch._get_parent_location()
2130
def set_parent(self, url):
2132
return self._real_branch.set_parent(url)
2134
def _set_parent_location(self, url):
2135
# Used by tests, to poke bad urls into branch configurations
2137
self.set_parent(url)
2140
return self._real_branch._set_parent_location(url)
2142
def set_stacked_on_url(self, stacked_location):
2143
"""Set the URL this branch is stacked against.
2145
:raises UnstackableBranchFormat: If the branch does not support
2147
:raises UnstackableRepositoryFormat: If the repository does not support
2151
return self._real_branch.set_stacked_on_url(stacked_location)
2154
def pull(self, source, overwrite=False, stop_revision=None,
2156
self._clear_cached_state_of_remote_branch_only()
2158
return self._real_branch.pull(
2159
source, overwrite=overwrite, stop_revision=stop_revision,
2160
_override_hook_target=self, **kwargs)
2163
def push(self, target, overwrite=False, stop_revision=None):
2165
return self._real_branch.push(
2166
target, overwrite=overwrite, stop_revision=stop_revision,
2167
_override_hook_source_branch=self)
2169
def is_locked(self):
2170
return self._lock_count >= 1
2173
def revision_id_to_revno(self, revision_id):
2175
return self._real_branch.revision_id_to_revno(revision_id)
2178
def set_last_revision_info(self, revno, revision_id):
2179
# XXX: These should be returned by the set_last_revision_info verb
2180
old_revno, old_revid = self.last_revision_info()
2181
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2182
revision_id = ensure_null(revision_id)
2184
response = self._call('Branch.set_last_revision_info',
2185
self._remote_path(), self._lock_token, self._repo_lock_token,
2186
str(revno), revision_id)
2187
except errors.UnknownSmartMethod:
2189
self._clear_cached_state_of_remote_branch_only()
2190
self._real_branch.set_last_revision_info(revno, revision_id)
2191
self._last_revision_info_cache = revno, revision_id
2193
if response == ('ok',):
2194
self._clear_cached_state()
2195
self._last_revision_info_cache = revno, revision_id
2196
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2197
# Update the _real_branch's cache too.
2198
if self._real_branch is not None:
2199
cache = self._last_revision_info_cache
2200
self._real_branch._last_revision_info_cache = cache
2202
raise errors.UnexpectedSmartServerResponse(response)
2205
def generate_revision_history(self, revision_id, last_rev=None,
2207
medium = self._client._medium
2208
if not medium._is_remote_before((1, 6)):
2209
# Use a smart method for 1.6 and above servers
2211
self._set_last_revision_descendant(revision_id, other_branch,
2212
allow_diverged=True, allow_overwrite_descendant=True)
2214
except errors.UnknownSmartMethod:
2215
medium._remember_remote_is_before((1, 6))
2216
self._clear_cached_state_of_remote_branch_only()
2217
self.set_revision_history(self._lefthand_history(revision_id,
2218
last_rev=last_rev,other_branch=other_branch))
2220
def set_push_location(self, location):
2222
return self._real_branch.set_push_location(location)
2225
def _extract_tar(tar, to_dir):
2226
"""Extract all the contents of a tarfile object.
2228
A replacement for extractall, which is not present in python2.4
2231
tar.extract(tarinfo, to_dir)
2234
def _translate_error(err, **context):
2235
"""Translate an ErrorFromSmartServer into a more useful error.
2237
Possible context keys:
2245
If the error from the server doesn't match a known pattern, then
2246
UnknownErrorFromSmartServer is raised.
2250
return context[name]
2251
except KeyError, key_err:
2252
mutter('Missing key %r in context %r', key_err.args[0], context)
2255
"""Get the path from the context if present, otherwise use first error
2259
return context['path']
2260
except KeyError, key_err:
2262
return err.error_args[0]
2263
except IndexError, idx_err:
2265
'Missing key %r in context %r', key_err.args[0], context)
2268
if err.error_verb == 'NoSuchRevision':
2269
raise NoSuchRevision(find('branch'), err.error_args[0])
2270
elif err.error_verb == 'nosuchrevision':
2271
raise NoSuchRevision(find('repository'), err.error_args[0])
2272
elif err.error_tuple == ('nobranch',):
2273
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2274
elif err.error_verb == 'norepository':
2275
raise errors.NoRepositoryPresent(find('bzrdir'))
2276
elif err.error_verb == 'LockContention':
2277
raise errors.LockContention('(remote lock)')
2278
elif err.error_verb == 'UnlockableTransport':
2279
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2280
elif err.error_verb == 'LockFailed':
2281
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2282
elif err.error_verb == 'TokenMismatch':
2283
raise errors.TokenMismatch(find('token'), '(remote token)')
2284
elif err.error_verb == 'Diverged':
2285
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2286
elif err.error_verb == 'TipChangeRejected':
2287
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2288
elif err.error_verb == 'UnstackableBranchFormat':
2289
raise errors.UnstackableBranchFormat(*err.error_args)
2290
elif err.error_verb == 'UnstackableRepositoryFormat':
2291
raise errors.UnstackableRepositoryFormat(*err.error_args)
2292
elif err.error_verb == 'NotStacked':
2293
raise errors.NotStacked(branch=find('branch'))
2294
elif err.error_verb == 'PermissionDenied':
2296
if len(err.error_args) >= 2:
2297
extra = err.error_args[1]
2300
raise errors.PermissionDenied(path, extra=extra)
2301
elif err.error_verb == 'ReadError':
2303
raise errors.ReadError(path)
2304
elif err.error_verb == 'NoSuchFile':
2306
raise errors.NoSuchFile(path)
2307
elif err.error_verb == 'FileExists':
2308
raise errors.FileExists(err.error_args[0])
2309
elif err.error_verb == 'DirectoryNotEmpty':
2310
raise errors.DirectoryNotEmpty(err.error_args[0])
2311
elif err.error_verb == 'ShortReadvError':
2312
args = err.error_args
2313
raise errors.ShortReadvError(
2314
args[0], int(args[1]), int(args[2]), int(args[3]))
2315
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2316
encoding = str(err.error_args[0]) # encoding must always be a string
2317
val = err.error_args[1]
2318
start = int(err.error_args[2])
2319
end = int(err.error_args[3])
2320
reason = str(err.error_args[4]) # reason must always be a string
2321
if val.startswith('u:'):
2322
val = val[2:].decode('utf-8')
2323
elif val.startswith('s:'):
2324
val = val[2:].decode('base64')
2325
if err.error_verb == 'UnicodeDecodeError':
2326
raise UnicodeDecodeError(encoding, val, start, end, reason)
2327
elif err.error_verb == 'UnicodeEncodeError':
2328
raise UnicodeEncodeError(encoding, val, start, end, reason)
2329
elif err.error_verb == 'ReadOnlyError':
2330
raise errors.TransportNotPossible('readonly transport')
2331
raise errors.UnknownErrorFromSmartServer(err)