13
13
# You should have received a copy of the GNU General Public License
14
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
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.
20
from cStringIO import StringIO
19
22
from bzrlib import (
22
bzrdir as _mod_bzrdir,
30
repository as _mod_repository,
31
revision as _mod_revision,
37
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
29
from bzrlib.branch import Branch, BranchReferenceFormat
30
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
31
from bzrlib.config import BranchConfig, TreeConfig
32
from bzrlib.decorators import needs_read_lock, needs_write_lock
33
from bzrlib.errors import NoSuchRevision
43
34
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.smart.client import _SmartClient
46
from bzrlib.revision import NULL_REVISION
47
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
48
from bzrlib.trace import mutter, note, warning
51
class _RpcHelper(object):
52
"""Mixin class that helps with issuing RPCs."""
54
def _call(self, method, *args, **err_context):
56
return self._client.call(method, *args)
57
except errors.ErrorFromSmartServer, err:
58
self._translate_error(err, **err_context)
60
def _call_expecting_body(self, method, *args, **err_context):
62
return self._client.call_expecting_body(method, *args)
63
except errors.ErrorFromSmartServer, err:
64
self._translate_error(err, **err_context)
66
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
68
return self._client.call_with_body_bytes(method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
72
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
75
return self._client.call_with_body_bytes_expecting_body(
76
method, args, body_bytes)
77
except errors.ErrorFromSmartServer, err:
78
self._translate_error(err, **err_context)
81
def response_tuple_to_repo_format(response):
82
"""Convert a response tuple describing a repository format to a format."""
83
format = RemoteRepositoryFormat()
84
format._rich_root_data = (response[0] == 'yes')
85
format._supports_tree_reference = (response[1] == 'yes')
86
format._supports_external_lookups = (response[2] == 'yes')
87
format._network_name = response[3]
91
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
92
# does not have to be imported unless a remote format is involved.
94
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
95
"""Format representing bzrdirs accessed via a smart server"""
97
supports_workingtrees = False
100
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
101
# XXX: It's a bit ugly that the network name is here, because we'd
102
# like to believe that format objects are stateless or at least
103
# immutable, However, we do at least avoid mutating the name after
104
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
105
self._network_name = None
108
return "%s(_network_name=%r)" % (self.__class__.__name__,
111
def get_format_description(self):
112
if self._network_name:
113
real_format = controldir.network_format_registry.get(self._network_name)
114
return 'Remote: ' + real_format.get_format_description()
115
return 'bzr remote bzrdir'
117
def get_format_string(self):
118
raise NotImplementedError(self.get_format_string)
120
def network_name(self):
121
if self._network_name:
122
return self._network_name
124
raise AssertionError("No network name set.")
126
def initialize_on_transport(self, transport):
128
# hand off the request to the smart server
129
client_medium = transport.get_smart_medium()
130
except errors.NoSmartMedium:
131
# TODO: lookup the local format from a server hint.
132
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
133
return local_dir_format.initialize_on_transport(transport)
134
client = _SmartClient(client_medium)
135
path = client.remote_path_from_transport(transport)
137
response = client.call('BzrDirFormat.initialize', path)
138
except errors.ErrorFromSmartServer, err:
139
_translate_error(err, path=path)
140
if response[0] != 'ok':
141
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
142
format = RemoteBzrDirFormat()
143
self._supply_sub_formats_to(format)
144
return RemoteBzrDir(transport, format)
146
def parse_NoneTrueFalse(self, arg):
153
raise AssertionError("invalid arg %r" % arg)
155
def _serialize_NoneTrueFalse(self, arg):
162
def _serialize_NoneString(self, arg):
165
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
166
create_prefix=False, force_new_repo=False, stacked_on=None,
167
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
170
# hand off the request to the smart server
171
client_medium = transport.get_smart_medium()
172
except errors.NoSmartMedium:
175
# Decline to open it if the server doesn't support our required
176
# version (3) so that the VFS-based transport will do it.
177
if client_medium.should_probe():
179
server_version = client_medium.protocol_version()
180
if server_version != '2':
184
except errors.SmartProtocolError:
185
# Apparently there's no usable smart server there, even though
186
# the medium supports the smart protocol.
191
client = _SmartClient(client_medium)
192
path = client.remote_path_from_transport(transport)
193
if client_medium._is_remote_before((1, 16)):
196
# TODO: lookup the local format from a server hint.
197
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
198
self._supply_sub_formats_to(local_dir_format)
199
return local_dir_format.initialize_on_transport_ex(transport,
200
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
201
force_new_repo=force_new_repo, stacked_on=stacked_on,
202
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
203
make_working_trees=make_working_trees, shared_repo=shared_repo,
205
return self._initialize_on_transport_ex_rpc(client, path, transport,
206
use_existing_dir, create_prefix, force_new_repo, stacked_on,
207
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
209
def _initialize_on_transport_ex_rpc(self, client, path, transport,
210
use_existing_dir, create_prefix, force_new_repo, stacked_on,
211
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
213
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
214
args.append(self._serialize_NoneTrueFalse(create_prefix))
215
args.append(self._serialize_NoneTrueFalse(force_new_repo))
216
args.append(self._serialize_NoneString(stacked_on))
217
# stack_on_pwd is often/usually our transport
220
stack_on_pwd = transport.relpath(stack_on_pwd)
223
except errors.PathNotChild:
225
args.append(self._serialize_NoneString(stack_on_pwd))
226
args.append(self._serialize_NoneString(repo_format_name))
227
args.append(self._serialize_NoneTrueFalse(make_working_trees))
228
args.append(self._serialize_NoneTrueFalse(shared_repo))
229
request_network_name = self._network_name or \
230
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
232
response = client.call('BzrDirFormat.initialize_ex_1.16',
233
request_network_name, path, *args)
234
except errors.UnknownSmartMethod:
235
client._medium._remember_remote_is_before((1,16))
236
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
237
self._supply_sub_formats_to(local_dir_format)
238
return local_dir_format.initialize_on_transport_ex(transport,
239
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
240
force_new_repo=force_new_repo, stacked_on=stacked_on,
241
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
242
make_working_trees=make_working_trees, shared_repo=shared_repo,
244
except errors.ErrorFromSmartServer, err:
245
_translate_error(err, path=path)
246
repo_path = response[0]
247
bzrdir_name = response[6]
248
require_stacking = response[7]
249
require_stacking = self.parse_NoneTrueFalse(require_stacking)
250
format = RemoteBzrDirFormat()
251
format._network_name = bzrdir_name
252
self._supply_sub_formats_to(format)
253
bzrdir = RemoteBzrDir(transport, format, _client=client)
255
repo_format = response_tuple_to_repo_format(response[1:])
259
repo_bzrdir_format = RemoteBzrDirFormat()
260
repo_bzrdir_format._network_name = response[5]
261
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
265
final_stack = response[8] or None
266
final_stack_pwd = response[9] or None
268
final_stack_pwd = urlutils.join(
269
transport.base, final_stack_pwd)
270
remote_repo = RemoteRepository(repo_bzr, repo_format)
271
if len(response) > 10:
272
# Updated server verb that locks remotely.
273
repo_lock_token = response[10] or None
274
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
276
remote_repo.dont_leave_lock_in_place()
278
remote_repo.lock_write()
279
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
280
final_stack_pwd, require_stacking)
281
policy.acquire_repository()
285
bzrdir._format.set_branch_format(self.get_branch_format())
287
# The repo has already been created, but we need to make sure that
288
# we'll make a stackable branch.
289
bzrdir._format.require_stacking(_skip_repo=True)
290
return remote_repo, bzrdir, require_stacking, policy
292
def _open(self, transport):
293
return RemoteBzrDir(transport, self)
295
def __eq__(self, other):
296
if not isinstance(other, RemoteBzrDirFormat):
298
return self.get_format_description() == other.get_format_description()
300
def __return_repository_format(self):
301
# Always return a RemoteRepositoryFormat object, but if a specific bzr
302
# repository format has been asked for, tell the RemoteRepositoryFormat
303
# that it should use that for init() etc.
304
result = RemoteRepositoryFormat()
305
custom_format = getattr(self, '_repository_format', None)
307
if isinstance(custom_format, RemoteRepositoryFormat):
310
# We will use the custom format to create repositories over the
311
# wire; expose its details like rich_root_data for code to
313
result._custom_format = custom_format
316
def get_branch_format(self):
317
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
318
if not isinstance(result, RemoteBranchFormat):
319
new_result = RemoteBranchFormat()
320
new_result._custom_format = result
322
self.set_branch_format(new_result)
326
repository_format = property(__return_repository_format,
327
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
330
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
35
from bzrlib.pack import ContainerReader
36
from bzrlib.smart import client, vfs
37
from bzrlib.symbol_versioning import (
41
from bzrlib.trace import note
43
# Note: RemoteBzrDirFormat is in bzrdir.py
45
class RemoteBzrDir(BzrDir):
331
46
"""Control directory on a remote server, accessed via bzr:// or similar."""
333
def __init__(self, transport, format, _client=None, _force_probe=False):
48
def __init__(self, transport, _client=None):
334
49
"""Construct a RemoteBzrDir.
336
51
:param _client: Private parameter for testing. Disables probing and the
337
52
use of a real bzrdir.
339
_mod_bzrdir.BzrDir.__init__(self, transport, format)
54
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
340
55
# this object holds a delegated bzrdir that uses file-level operations
341
56
# to talk to the other side
342
57
self._real_bzrdir = None
343
self._has_working_tree = None
344
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
345
# create_branch for details.
346
self._next_open_branch_result = None
348
59
if _client is None:
349
medium = transport.get_smart_medium()
350
self._client = client._SmartClient(medium)
60
self._shared_medium = transport.get_shared_medium()
61
self._client = client._SmartClient(self._shared_medium)
352
63
self._client = _client
359
return '%s(%r)' % (self.__class__.__name__, self._client)
361
def _probe_bzrdir(self):
362
medium = self._client._medium
64
self._shared_medium = None
363
67
path = self._path_for_remote_call(self._client)
364
if medium._is_remote_before((2, 1)):
368
self._rpc_open_2_1(path)
370
except errors.UnknownSmartMethod:
371
medium._remember_remote_is_before((2, 1))
374
def _rpc_open_2_1(self, path):
375
response = self._call('BzrDir.open_2.1', path)
376
if response == ('no',):
377
raise errors.NotBranchError(path=self.root_transport.base)
378
elif response[0] == 'yes':
379
if response[1] == 'yes':
380
self._has_working_tree = True
381
elif response[1] == 'no':
382
self._has_working_tree = False
384
raise errors.UnexpectedSmartServerResponse(response)
386
raise errors.UnexpectedSmartServerResponse(response)
388
def _rpc_open(self, path):
389
response = self._call('BzrDir.open', path)
68
response = self._client.call('BzrDir.open', path)
390
69
if response not in [('yes',), ('no',)]:
391
70
raise errors.UnexpectedSmartServerResponse(response)
392
71
if response == ('no',):
393
raise errors.NotBranchError(path=self.root_transport.base)
72
raise errors.NotBranchError(path=transport.base)
395
74
def _ensure_real(self):
396
75
"""Ensure that there is a _real_bzrdir set.
398
77
Used before calls to self._real_bzrdir.
400
79
if not self._real_bzrdir:
401
if 'hpssvfs' in debug.debug_flags:
403
warning('VFS BzrDir access triggered\n%s',
404
''.join(traceback.format_stack()))
405
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
80
self._real_bzrdir = BzrDir.open_from_transport(
406
81
self.root_transport, _server_formats=False)
407
self._format._network_name = \
408
self._real_bzrdir._format.network_name()
410
def _translate_error(self, err, **context):
411
_translate_error(err, bzrdir=self, **context)
413
def break_lock(self):
414
# Prevent aliasing problems in the next_open_branch_result cache.
415
# See create_branch for rationale.
416
self._next_open_branch_result = None
417
return _mod_bzrdir.BzrDir.break_lock(self)
419
def _vfs_cloning_metadir(self, require_stacking=False):
421
return self._real_bzrdir.cloning_metadir(
422
require_stacking=require_stacking)
424
def cloning_metadir(self, require_stacking=False):
425
medium = self._client._medium
426
if medium._is_remote_before((1, 13)):
427
return self._vfs_cloning_metadir(require_stacking=require_stacking)
428
verb = 'BzrDir.cloning_metadir'
433
path = self._path_for_remote_call(self._client)
435
response = self._call(verb, path, stacking)
436
except errors.UnknownSmartMethod:
437
medium._remember_remote_is_before((1, 13))
438
return self._vfs_cloning_metadir(require_stacking=require_stacking)
439
except errors.UnknownErrorFromSmartServer, err:
440
if err.error_tuple != ('BranchReference',):
442
# We need to resolve the branch reference to determine the
443
# cloning_metadir. This causes unnecessary RPCs to open the
444
# referenced branch (and bzrdir, etc) but only when the caller
445
# didn't already resolve the branch reference.
446
referenced_branch = self.open_branch()
447
return referenced_branch.bzrdir.cloning_metadir()
448
if len(response) != 3:
449
raise errors.UnexpectedSmartServerResponse(response)
450
control_name, repo_name, branch_info = response
451
if len(branch_info) != 2:
452
raise errors.UnexpectedSmartServerResponse(response)
453
branch_ref, branch_name = branch_info
454
format = controldir.network_format_registry.get(control_name)
456
format.repository_format = _mod_repository.network_format_registry.get(
458
if branch_ref == 'ref':
459
# XXX: we need possible_transports here to avoid reopening the
460
# connection to the referenced location
461
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
462
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
463
format.set_branch_format(branch_format)
464
elif branch_ref == 'branch':
466
format.set_branch_format(
467
branch.network_format_registry.get(branch_name))
469
raise errors.UnexpectedSmartServerResponse(response)
472
83
def create_repository(self, shared=False):
473
# as per meta1 formats - just delegate to the format object which may
475
result = self._format.repository_format.initialize(self, shared)
476
if not isinstance(result, RemoteRepository):
477
return self.open_repository()
85
self._real_bzrdir.create_repository(shared=shared)
86
return self.open_repository()
481
88
def destroy_repository(self):
482
89
"""See BzrDir.destroy_repository"""
483
90
self._ensure_real()
484
91
self._real_bzrdir.destroy_repository()
486
def create_branch(self, name=None, repository=None):
487
# as per meta1 formats - just delegate to the format object which may
489
real_branch = self._format.get_branch_format().initialize(self,
490
name=name, repository=repository)
491
if not isinstance(real_branch, RemoteBranch):
492
if not isinstance(repository, RemoteRepository):
493
raise AssertionError(
494
'need a RemoteRepository to use with RemoteBranch, got %r'
496
result = RemoteBranch(self, repository, real_branch, name=name)
499
# BzrDir.clone_on_transport() uses the result of create_branch but does
500
# not return it to its callers; we save approximately 8% of our round
501
# trips by handing the branch we created back to the first caller to
502
# open_branch rather than probing anew. Long term we need a API in
503
# bzrdir that doesn't discard result objects (like result_branch).
505
self._next_open_branch_result = result
93
def create_branch(self):
95
real_branch = self._real_bzrdir.create_branch()
96
return RemoteBranch(self, self.find_repository(), real_branch)
508
def destroy_branch(self, name=None):
98
def destroy_branch(self):
509
99
"""See BzrDir.destroy_branch"""
510
100
self._ensure_real()
511
self._real_bzrdir.destroy_branch(name=name)
512
self._next_open_branch_result = None
101
self._real_bzrdir.destroy_branch()
514
def create_workingtree(self, revision_id=None, from_branch=None,
515
accelerator_tree=None, hardlink=False):
103
def create_workingtree(self, revision_id=None, from_branch=None):
516
104
raise errors.NotLocalUrl(self.transport.base)
518
def find_branch_format(self, name=None):
106
def find_branch_format(self):
519
107
"""Find the branch 'format' for this bzrdir.
521
109
This might be a synthetic object for e.g. RemoteBranch and SVN.
523
b = self.open_branch(name=name)
111
b = self.open_branch()
526
def get_branch_reference(self, name=None):
114
def get_branch_reference(self):
527
115
"""See BzrDir.get_branch_reference()."""
529
# XXX JRV20100304: Support opening colocated branches
530
raise errors.NoColocatedBranchSupport(self)
531
response = self._get_branch_reference()
532
if response[0] == 'ref':
537
def _get_branch_reference(self):
538
116
path = self._path_for_remote_call(self._client)
539
medium = self._client._medium
541
('BzrDir.open_branchV3', (2, 1)),
542
('BzrDir.open_branchV2', (1, 13)),
543
('BzrDir.open_branch', None),
545
for verb, required_version in candidate_calls:
546
if required_version and medium._is_remote_before(required_version):
549
response = self._call(verb, path)
550
except errors.UnknownSmartMethod:
551
if required_version is None:
553
medium._remember_remote_is_before(required_version)
556
if verb == 'BzrDir.open_branch':
557
if response[0] != 'ok':
558
raise errors.UnexpectedSmartServerResponse(response)
559
if response[1] != '':
560
return ('ref', response[1])
562
return ('branch', '')
563
if response[0] not in ('ref', 'branch'):
117
response = self._client.call('BzrDir.open_branch', path)
118
if response[0] == 'ok':
119
if response[1] == '':
120
# branch at this location.
123
# a branch reference, use the existing BranchReference logic.
125
elif response == ('nobranch',):
126
raise errors.NotBranchError(path=self.root_transport.base)
564
128
raise errors.UnexpectedSmartServerResponse(response)
567
def _get_tree_branch(self, name=None):
568
"""See BzrDir._get_tree_branch()."""
569
return None, self.open_branch(name=name)
571
def open_branch(self, name=None, unsupported=False,
572
ignore_fallbacks=False):
574
raise NotImplementedError('unsupported flag support not implemented yet.')
575
if self._next_open_branch_result is not None:
576
# See create_branch for details.
577
result = self._next_open_branch_result
578
self._next_open_branch_result = None
580
response = self._get_branch_reference()
581
if response[0] == 'ref':
130
def open_branch(self, _unsupported=False):
131
assert _unsupported == False, 'unsupported flag support not implemented yet.'
132
reference_url = self.get_branch_reference()
133
if reference_url is None:
134
# branch at this location.
135
return RemoteBranch(self, self.find_repository())
582
137
# a branch reference, use the existing BranchReference logic.
583
138
format = BranchReferenceFormat()
584
return format.open(self, name=name, _found=True,
585
location=response[1], ignore_fallbacks=ignore_fallbacks)
586
branch_format_name = response[1]
587
if not branch_format_name:
588
branch_format_name = None
589
format = RemoteBranchFormat(network_name=branch_format_name)
590
return RemoteBranch(self, self.find_repository(), format=format,
591
setup_stacking=not ignore_fallbacks, name=name)
593
def _open_repo_v1(self, path):
594
verb = 'BzrDir.find_repository'
595
response = self._call(verb, path)
596
if response[0] != 'ok':
597
raise errors.UnexpectedSmartServerResponse(response)
598
# servers that only support the v1 method don't support external
601
repo = self._real_bzrdir.open_repository()
602
response = response + ('no', repo._format.network_name())
603
return response, repo
605
def _open_repo_v2(self, path):
606
verb = 'BzrDir.find_repositoryV2'
607
response = self._call(verb, path)
608
if response[0] != 'ok':
609
raise errors.UnexpectedSmartServerResponse(response)
611
repo = self._real_bzrdir.open_repository()
612
response = response + (repo._format.network_name(),)
613
return response, repo
615
def _open_repo_v3(self, path):
616
verb = 'BzrDir.find_repositoryV3'
617
medium = self._client._medium
618
if medium._is_remote_before((1, 13)):
619
raise errors.UnknownSmartMethod(verb)
621
response = self._call(verb, path)
622
except errors.UnknownSmartMethod:
623
medium._remember_remote_is_before((1, 13))
625
if response[0] != 'ok':
626
raise errors.UnexpectedSmartServerResponse(response)
627
return response, None
139
return format.open(self, _found=True, location=reference_url)
629
141
def open_repository(self):
630
142
path = self._path_for_remote_call(self._client)
632
for probe in [self._open_repo_v3, self._open_repo_v2,
635
response, real_repo = probe(path)
637
except errors.UnknownSmartMethod:
640
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
641
if response[0] != 'ok':
642
raise errors.UnexpectedSmartServerResponse(response)
643
if len(response) != 6:
644
raise SmartProtocolError('incorrect response length %s' % (response,))
143
response = self._client.call('BzrDir.find_repository', path)
144
assert response[0] in ('ok', 'norepository'), \
145
'unexpected response code %s' % (response,)
146
if response[0] == 'norepository':
147
raise errors.NoRepositoryPresent(self)
148
assert len(response) == 4, 'incorrect response length %s' % (response,)
645
149
if response[1] == '':
646
# repo is at this dir.
647
format = response_tuple_to_repo_format(response[2:])
648
# Used to support creating a real format instance when needed.
649
format._creating_bzrdir = self
650
remote_repo = RemoteRepository(self, format)
651
format._creating_repo = remote_repo
652
if real_repo is not None:
653
remote_repo._set_real_repository(real_repo)
150
format = RemoteRepositoryFormat()
151
format.rich_root_data = (response[2] == 'yes')
152
format.supports_tree_reference = (response[3] == 'yes')
153
return RemoteRepository(self, format)
656
155
raise errors.NoRepositoryPresent(self)
658
def has_workingtree(self):
659
if self._has_working_tree is None:
661
self._has_working_tree = self._real_bzrdir.has_workingtree()
662
return self._has_working_tree
664
157
def open_workingtree(self, recommend_upgrade=True):
665
if self.has_workingtree():
159
if self._real_bzrdir.has_workingtree():
666
160
raise errors.NotLocalUrl(self.root_transport)
668
162
raise errors.NoWorkingTree(self.root_transport.base)
687
181
"""Upgrading of remote bzrdirs is not supported yet."""
690
def needs_format_conversion(self, format):
184
def needs_format_conversion(self, format=None):
691
185
"""Upgrading of remote bzrdirs is not supported yet."""
694
def clone(self, url, revision_id=None, force_new_repo=False,
695
preserve_stacking=False):
188
def clone(self, url, revision_id=None, force_new_repo=False):
696
189
self._ensure_real()
697
190
return self._real_bzrdir.clone(url, revision_id=revision_id,
698
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
700
def _get_config(self):
701
return RemoteBzrDirConfig(self)
704
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
191
force_new_repo=force_new_repo)
194
class RemoteRepositoryFormat(repository.RepositoryFormat):
705
195
"""Format for repositories accessed over a _SmartClient.
707
197
Instances of this repository are represented by RemoteRepository
710
The RemoteRepositoryFormat is parameterized during construction
200
The RemoteRepositoryFormat is parameterised during construction
711
201
to reflect the capabilities of the real, remote format. Specifically
712
202
the attributes rich_root_data and supports_tree_reference are set
713
203
on a per instance basis, and are not set (and should not be) at
716
:ivar _custom_format: If set, a specific concrete repository format that
717
will be used when initializing a repository with this
718
RemoteRepositoryFormat.
719
:ivar _creating_repo: If set, the repository object that this
720
RemoteRepositoryFormat was created for: it can be called into
721
to obtain data like the network name.
724
_matchingbzrdir = RemoteBzrDirFormat()
725
supports_full_versioned_files = True
726
supports_leaving_lock = True
729
_mod_repository.RepositoryFormat.__init__(self)
730
self._custom_format = None
731
self._network_name = None
732
self._creating_bzrdir = None
733
self._revision_graph_can_have_wrong_parents = None
734
self._supports_chks = None
735
self._supports_external_lookups = None
736
self._supports_tree_reference = None
737
self._supports_funky_characters = None
738
self._rich_root_data = None
741
return "%s(_network_name=%r)" % (self.__class__.__name__,
745
def fast_deltas(self):
747
return self._custom_format.fast_deltas
750
def rich_root_data(self):
751
if self._rich_root_data is None:
753
self._rich_root_data = self._custom_format.rich_root_data
754
return self._rich_root_data
757
def supports_chks(self):
758
if self._supports_chks is None:
760
self._supports_chks = self._custom_format.supports_chks
761
return self._supports_chks
764
def supports_external_lookups(self):
765
if self._supports_external_lookups is None:
767
self._supports_external_lookups = \
768
self._custom_format.supports_external_lookups
769
return self._supports_external_lookups
772
def supports_funky_characters(self):
773
if self._supports_funky_characters is None:
775
self._supports_funky_characters = \
776
self._custom_format.supports_funky_characters
777
return self._supports_funky_characters
780
def supports_tree_reference(self):
781
if self._supports_tree_reference is None:
783
self._supports_tree_reference = \
784
self._custom_format.supports_tree_reference
785
return self._supports_tree_reference
788
def revision_graph_can_have_wrong_parents(self):
789
if self._revision_graph_can_have_wrong_parents is None:
791
self._revision_graph_can_have_wrong_parents = \
792
self._custom_format.revision_graph_can_have_wrong_parents
793
return self._revision_graph_can_have_wrong_parents
795
def _vfs_initialize(self, a_bzrdir, shared):
796
"""Helper for common code in initialize."""
797
if self._custom_format:
798
# Custom format requested
799
result = self._custom_format.initialize(a_bzrdir, shared=shared)
800
elif self._creating_bzrdir is not None:
801
# Use the format that the repository we were created to back
803
prior_repo = self._creating_bzrdir.open_repository()
804
prior_repo._ensure_real()
805
result = prior_repo._real_repository._format.initialize(
806
a_bzrdir, shared=shared)
808
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
809
# support remote initialization.
810
# We delegate to a real object at this point (as RemoteBzrDir
811
# delegate to the repository format which would lead to infinite
812
# recursion if we just called a_bzrdir.create_repository.
813
a_bzrdir._ensure_real()
814
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
815
if not isinstance(result, RemoteRepository):
816
return self.open(a_bzrdir)
207
_matchingbzrdir = RemoteBzrDirFormat
820
209
def initialize(self, a_bzrdir, shared=False):
821
# Being asked to create on a non RemoteBzrDir:
822
if not isinstance(a_bzrdir, RemoteBzrDir):
823
return self._vfs_initialize(a_bzrdir, shared)
824
medium = a_bzrdir._client._medium
825
if medium._is_remote_before((1, 13)):
826
return self._vfs_initialize(a_bzrdir, shared)
827
# Creating on a remote bzr dir.
828
# 1) get the network name to use.
829
if self._custom_format:
830
network_name = self._custom_format.network_name()
831
elif self._network_name:
832
network_name = self._network_name
834
# Select the current bzrlib default and ask for that.
835
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
836
reference_format = reference_bzrdir_format.repository_format
837
network_name = reference_format.network_name()
838
# 2) try direct creation via RPC
839
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
840
verb = 'BzrDir.create_repository'
846
response = a_bzrdir._call(verb, path, network_name, shared_str)
847
except errors.UnknownSmartMethod:
848
# Fallback - use vfs methods
849
medium._remember_remote_is_before((1, 13))
850
return self._vfs_initialize(a_bzrdir, shared)
852
# Turn the response into a RemoteRepository object.
853
format = response_tuple_to_repo_format(response[1:])
854
# Used to support creating a real format instance when needed.
855
format._creating_bzrdir = a_bzrdir
856
remote_repo = RemoteRepository(a_bzrdir, format)
857
format._creating_repo = remote_repo
210
assert isinstance(a_bzrdir, RemoteBzrDir), \
211
'%r is not a RemoteBzrDir' % (a_bzrdir,)
212
return a_bzrdir.create_repository(shared=shared)
860
214
def open(self, a_bzrdir):
861
if not isinstance(a_bzrdir, RemoteBzrDir):
862
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
215
assert isinstance(a_bzrdir, RemoteBzrDir)
863
216
return a_bzrdir.open_repository()
865
def _ensure_real(self):
866
if self._custom_format is None:
867
self._custom_format = _mod_repository.network_format_registry.get(
871
def _fetch_order(self):
873
return self._custom_format._fetch_order
876
def _fetch_uses_deltas(self):
878
return self._custom_format._fetch_uses_deltas
881
def _fetch_reconcile(self):
883
return self._custom_format._fetch_reconcile
885
218
def get_format_description(self):
887
return 'Remote: ' + self._custom_format.get_format_description()
219
return 'bzr remote repository'
889
221
def __eq__(self, other):
890
return self.__class__ is other.__class__
892
def network_name(self):
893
if self._network_name:
894
return self._network_name
895
self._creating_repo._ensure_real()
896
return self._creating_repo._real_repository._format.network_name()
899
def pack_compresses(self):
901
return self._custom_format.pack_compresses
904
def _serializer(self):
906
return self._custom_format._serializer
909
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
910
controldir.ControlComponent):
222
return self.__class__ == other.__class__
224
def check_conversion_target(self, target_format):
225
if self.rich_root_data and not target_format.rich_root_data:
226
raise errors.BadConversionTarget(
227
'Does not support rich root data.', target_format)
228
if (self.supports_tree_reference and
229
not getattr(target_format, 'supports_tree_reference', False)):
230
raise errors.BadConversionTarget(
231
'Does not support nested trees', target_format)
234
class RemoteRepository(object):
911
235
"""Repository accessed over rpc.
913
237
For the moment most operations are performed using local transport-backed
1099
335
self._ensure_real()
1100
336
return self._real_repository._generate_text_key_index()
1102
def _get_revision_graph(self, revision_id):
1103
"""Private method for using with old (< 1.2) servers to fallback."""
338
def get_revision_graph(self, revision_id=None):
339
"""See Repository.get_revision_graph()."""
1104
340
if revision_id is None:
1105
341
revision_id = ''
1106
elif _mod_revision.is_null(revision_id):
342
elif revision.is_null(revision_id):
1109
345
path = self.bzrdir._path_for_remote_call(self._client)
1110
response = self._call_expecting_body(
346
assert type(revision_id) is str
347
response = self._client.call_expecting_body(
1111
348
'Repository.get_revision_graph', path, revision_id)
1112
response_tuple, response_handler = response
1113
if response_tuple[0] != 'ok':
1114
raise errors.UnexpectedSmartServerResponse(response_tuple)
1115
coded = response_handler.read_body_bytes()
1117
# no revisions in this repository!
1119
lines = coded.split('\n')
1122
d = tuple(line.split())
1123
revision_graph[d[0]] = d[1:]
1125
return revision_graph
1127
def _get_sink(self):
1128
"""See Repository._get_sink()."""
1129
return RemoteStreamSink(self)
1131
def _get_source(self, to_format):
1132
"""Return a source for streaming from this repository."""
1133
return RemoteStreamSource(self, to_format)
1136
def get_file_graph(self):
1137
return graph.Graph(self.texts)
349
if response[0][0] not in ['ok', 'nosuchrevision']:
350
raise errors.UnexpectedSmartServerResponse(response[0])
351
if response[0][0] == 'ok':
352
coded = response[1].read_body_bytes()
354
# no revisions in this repository!
356
lines = coded.split('\n')
359
d = tuple(line.split())
360
revision_graph[d[0]] = d[1:]
362
return revision_graph
364
response_body = response[1].read_body_bytes()
365
assert response_body == ''
366
raise NoSuchRevision(self, revision_id)
1140
368
def has_revision(self, revision_id):
1141
"""True if this repository has a copy of the revision."""
1142
# Copy of bzrlib.repository.Repository.has_revision
1143
return revision_id in self.has_revisions((revision_id,))
1146
def has_revisions(self, revision_ids):
1147
"""Probe to find out the presence of multiple revisions.
1149
:param revision_ids: An iterable of revision_ids.
1150
:return: A set of the revision_ids that were present.
1152
# Copy of bzrlib.repository.Repository.has_revisions
1153
parent_map = self.get_parent_map(revision_ids)
1154
result = set(parent_map)
1155
if _mod_revision.NULL_REVISION in revision_ids:
1156
result.add(_mod_revision.NULL_REVISION)
1159
def _has_same_fallbacks(self, other_repo):
1160
"""Returns true if the repositories have the same fallbacks."""
1161
# XXX: copied from Repository; it should be unified into a base class
1162
# <https://bugs.launchpad.net/bzr/+bug/401622>
1163
my_fb = self._fallback_repositories
1164
other_fb = other_repo._fallback_repositories
1165
if len(my_fb) != len(other_fb):
1167
for f, g in zip(my_fb, other_fb):
1168
if not f.has_same_location(g):
369
"""See Repository.has_revision()."""
370
if revision_id is None:
371
# The null revision is always present.
373
path = self.bzrdir._path_for_remote_call(self._client)
374
response = self._client.call('Repository.has_revision', path, revision_id)
375
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
376
return response[0] == 'yes'
1172
378
def has_same_location(self, other):
1173
# TODO: Move to RepositoryBase and unify with the regular Repository
1174
# one; unfortunately the tests rely on slightly different behaviour at
1175
# present -- mbp 20090710
1176
return (self.__class__ is other.__class__ and
379
return (self.__class__ == other.__class__ and
1177
380
self.bzrdir.transport.base == other.bzrdir.transport.base)
1179
382
def get_graph(self, other_repository=None):
1180
383
"""Return the graph for this repository format"""
1181
parents_provider = self._make_parents_provider(other_repository)
1182
return graph.Graph(parents_provider)
1185
def get_known_graph_ancestry(self, revision_ids):
1186
"""Return the known graph for a set of revision ids and their ancestors.
1188
st = static_tuple.StaticTuple
1189
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1190
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1191
return graph.GraphThunkIdsToKeys(known_graph)
385
return self._real_repository.get_graph(other_repository)
1193
387
def gather_stats(self, revid=None, committers=None):
1194
388
"""See Repository.gather_stats()."""
1195
389
path = self.bzrdir._path_for_remote_call(self._client)
1196
390
# revid can be None to indicate no revisions, not just NULL_REVISION
1197
if revid is None or _mod_revision.is_null(revid):
391
if revid is None or revision.is_null(revid):
1200
394
fmt_revid = revid
1571
658
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1573
660
def make_working_trees(self):
1574
"""See Repository.make_working_trees"""
1576
return self._real_repository.make_working_trees()
1578
def refresh_data(self):
1579
"""Re-read any data needed to synchronise with disk.
1581
This method is intended to be called after another repository instance
1582
(such as one used by a smart server) has inserted data into the
1583
repository. On all repositories this will work outside of write groups.
1584
Some repository formats (pack and newer for bzrlib native formats)
1585
support refresh_data inside write groups. If called inside a write
1586
group on a repository that does not support refreshing in a write group
1587
IsInWriteGroupError will be raised.
1589
if self._real_repository is not None:
1590
self._real_repository.refresh_data()
1592
def revision_ids_to_search_result(self, result_set):
1593
"""Convert a set of revision ids to a graph SearchResult."""
1594
result_parents = set()
1595
for parents in self.get_graph().get_parent_map(
1596
result_set).itervalues():
1597
result_parents.update(parents)
1598
included_keys = result_set.intersection(result_parents)
1599
start_keys = result_set.difference(included_keys)
1600
exclude_keys = result_parents.difference(result_set)
1601
result = graph.SearchResult(start_keys, exclude_keys,
1602
len(result_set), result_set)
1606
def search_missing_revision_ids(self, other,
1607
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1608
find_ghosts=True, revision_ids=None, if_present_ids=None,
1610
"""Return the revision ids that other has that this does not.
1612
These are returned in topological order.
1614
revision_id: only return revision ids included by revision_id.
1616
if symbol_versioning.deprecated_passed(revision_id):
1617
symbol_versioning.warn(
1618
'search_missing_revision_ids(revision_id=...) was '
1619
'deprecated in 2.4. Use revision_ids=[...] instead.',
1620
DeprecationWarning, stacklevel=2)
1621
if revision_ids is not None:
1622
raise AssertionError(
1623
'revision_ids is mutually exclusive with revision_id')
1624
if revision_id is not None:
1625
revision_ids = [revision_id]
1626
inter_repo = _mod_repository.InterRepository.get(other, self)
1627
return inter_repo.search_missing_revision_ids(
1628
find_ghosts=find_ghosts, revision_ids=revision_ids,
1629
if_present_ids=if_present_ids, limit=limit)
1631
def fetch(self, source, revision_id=None, find_ghosts=False,
1633
# No base implementation to use as RemoteRepository is not a subclass
1634
# of Repository; so this is a copy of Repository.fetch().
1635
if fetch_spec is not None and revision_id is not None:
1636
raise AssertionError(
1637
"fetch_spec and revision_id are mutually exclusive.")
1638
if self.is_in_write_group():
1639
raise errors.InternalBzrError(
1640
"May not fetch while in a write group.")
1641
# fast path same-url fetch operations
1642
if (self.has_same_location(source)
1643
and fetch_spec is None
1644
and self._has_same_fallbacks(source)):
661
"""RemoteRepositories never create working trees by default."""
664
def fetch(self, source, revision_id=None, pb=None):
665
if self.has_same_location(source):
1645
666
# check that last_revision is in 'from' and then return a
1647
668
if (revision_id is not None and
1648
not _mod_revision.is_null(revision_id)):
669
not revision.is_null(revision_id)):
1649
670
self.get_revision(revision_id)
1651
# if there is no specific appropriate InterRepository, this will get
1652
# the InterRepository base class, which raises an
1653
# IncompatibleRepositories when asked to fetch.
1654
inter = _mod_repository.InterRepository.get(source, self)
1655
return inter.fetch(revision_id=revision_id,
1656
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
673
return self._real_repository.fetch(
674
source, revision_id=revision_id, pb=pb)
1658
676
def create_bundle(self, target, base, fileobj, format=None):
1659
677
self._ensure_real()
1660
678
self._real_repository.create_bundle(target, base, fileobj, format)
681
def control_weaves(self):
683
return self._real_repository.control_weaves
1662
685
@needs_read_lock
1663
@symbol_versioning.deprecated_method(
1664
symbol_versioning.deprecated_in((2, 4, 0)))
1665
686
def get_ancestry(self, revision_id, topo_sorted=True):
1666
687
self._ensure_real()
1667
688
return self._real_repository.get_ancestry(revision_id, topo_sorted)
691
def get_inventory_weave(self):
693
return self._real_repository.get_inventory_weave()
1669
695
def fileids_altered_by_revision_ids(self, revision_ids):
1670
696
self._ensure_real()
1671
697
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1674
700
self._ensure_real()
1675
701
return self._real_repository._get_versioned_file_checker(
1676
702
revisions, revision_versions_cache)
1678
704
def iter_files_bytes(self, desired_files):
1679
705
"""See Repository.iter_file_bytes.
1681
707
self._ensure_real()
1682
708
return self._real_repository.iter_files_bytes(desired_files)
1684
def get_parent_map(self, revision_ids):
1685
"""See bzrlib.Graph.get_parent_map()."""
1686
return self._make_parents_provider().get_parent_map(revision_ids)
1688
def _get_parent_map_rpc(self, keys):
1689
"""Helper for get_parent_map that performs the RPC."""
1690
medium = self._client._medium
1691
if medium._is_remote_before((1, 2)):
1692
# We already found out that the server can't understand
1693
# Repository.get_parent_map requests, so just fetch the whole
1696
# Note that this reads the whole graph, when only some keys are
1697
# wanted. On this old server there's no way (?) to get them all
1698
# in one go, and the user probably will have seen a warning about
1699
# the server being old anyhow.
1700
rg = self._get_revision_graph(None)
1701
# There is an API discrepancy between get_parent_map and
1702
# get_revision_graph. Specifically, a "key:()" pair in
1703
# get_revision_graph just means a node has no parents. For
1704
# "get_parent_map" it means the node is a ghost. So fix up the
1705
# graph to correct this.
1706
# https://bugs.launchpad.net/bzr/+bug/214894
1707
# There is one other "bug" which is that ghosts in
1708
# get_revision_graph() are not returned at all. But we won't worry
1709
# about that for now.
1710
for node_id, parent_ids in rg.iteritems():
1711
if parent_ids == ():
1712
rg[node_id] = (NULL_REVISION,)
1713
rg[NULL_REVISION] = ()
1718
raise ValueError('get_parent_map(None) is not valid')
1719
if NULL_REVISION in keys:
1720
keys.discard(NULL_REVISION)
1721
found_parents = {NULL_REVISION:()}
1723
return found_parents
1726
# TODO(Needs analysis): We could assume that the keys being requested
1727
# from get_parent_map are in a breadth first search, so typically they
1728
# will all be depth N from some common parent, and we don't have to
1729
# have the server iterate from the root parent, but rather from the
1730
# keys we're searching; and just tell the server the keyspace we
1731
# already have; but this may be more traffic again.
1733
# Transform self._parents_map into a search request recipe.
1734
# TODO: Manage this incrementally to avoid covering the same path
1735
# repeatedly. (The server will have to on each request, but the less
1736
# work done the better).
1738
# Negative caching notes:
1739
# new server sends missing when a request including the revid
1740
# 'include-missing:' is present in the request.
1741
# missing keys are serialised as missing:X, and we then call
1742
# provider.note_missing(X) for-all X
1743
parents_map = self._unstacked_provider.get_cached_map()
1744
if parents_map is None:
1745
# Repository is not locked, so there's no cache.
1747
# start_set is all the keys in the cache
1748
start_set = set(parents_map)
1749
# result set is all the references to keys in the cache
1750
result_parents = set()
1751
for parents in parents_map.itervalues():
1752
result_parents.update(parents)
1753
stop_keys = result_parents.difference(start_set)
1754
# We don't need to send ghosts back to the server as a position to
1756
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1757
key_count = len(parents_map)
1758
if (NULL_REVISION in result_parents
1759
and NULL_REVISION in self._unstacked_provider.missing_keys):
1760
# If we pruned NULL_REVISION from the stop_keys because it's also
1761
# in our cache of "missing" keys we need to increment our key count
1762
# by 1, because the reconsitituted SearchResult on the server will
1763
# still consider NULL_REVISION to be an included key.
1765
included_keys = start_set.intersection(result_parents)
1766
start_set.difference_update(included_keys)
1767
recipe = ('manual', start_set, stop_keys, key_count)
1768
body = self._serialise_search_recipe(recipe)
1769
path = self.bzrdir._path_for_remote_call(self._client)
1771
if type(key) is not str:
1773
"key %r not a plain string" % (key,))
1774
verb = 'Repository.get_parent_map'
1775
args = (path, 'include-missing:') + tuple(keys)
1777
response = self._call_with_body_bytes_expecting_body(
1779
except errors.UnknownSmartMethod:
1780
# Server does not support this method, so get the whole graph.
1781
# Worse, we have to force a disconnection, because the server now
1782
# doesn't realise it has a body on the wire to consume, so the
1783
# only way to recover is to abandon the connection.
1785
'Server is too old for fast get_parent_map, reconnecting. '
1786
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1788
# To avoid having to disconnect repeatedly, we keep track of the
1789
# fact the server doesn't understand remote methods added in 1.2.
1790
medium._remember_remote_is_before((1, 2))
1791
# Recurse just once and we should use the fallback code.
1792
return self._get_parent_map_rpc(keys)
1793
response_tuple, response_handler = response
1794
if response_tuple[0] not in ['ok']:
1795
response_handler.cancel_read_body()
1796
raise errors.UnexpectedSmartServerResponse(response_tuple)
1797
if response_tuple[0] == 'ok':
1798
coded = bz2.decompress(response_handler.read_body_bytes())
1800
# no revisions found
1802
lines = coded.split('\n')
1805
d = tuple(line.split())
1807
revision_graph[d[0]] = d[1:]
1810
if d[0].startswith('missing:'):
1812
self._unstacked_provider.note_missing_key(revid)
1814
# no parents - so give the Graph result
1816
revision_graph[d[0]] = (NULL_REVISION,)
1817
return revision_graph
1819
710
@needs_read_lock
1820
711
def get_signature_text(self, revision_id):
1821
712
self._ensure_real()
1822
713
return self._real_repository.get_signature_text(revision_id)
1824
715
@needs_read_lock
1825
def _get_inventory_xml(self, revision_id):
1827
return self._real_repository._get_inventory_xml(revision_id)
716
def get_revision_graph_with_ghosts(self, revision_ids=None):
718
return self._real_repository.get_revision_graph_with_ghosts(
719
revision_ids=revision_ids)
722
def get_inventory_xml(self, revision_id):
724
return self._real_repository.get_inventory_xml(revision_id)
726
def deserialise_inventory(self, revision_id, xml):
728
return self._real_repository.deserialise_inventory(revision_id, xml)
1829
730
def reconcile(self, other=None, thorough=False):
1830
731
self._ensure_real()
1831
732
return self._real_repository.reconcile(other=other, thorough=thorough)
1833
734
def all_revision_ids(self):
1834
735
self._ensure_real()
1835
736
return self._real_repository.all_revision_ids()
1838
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1840
return self._real_repository.get_deltas_for_revisions(revisions,
1841
specific_fileids=specific_fileids)
1844
def get_revision_delta(self, revision_id, specific_fileids=None):
1846
return self._real_repository.get_revision_delta(revision_id,
1847
specific_fileids=specific_fileids)
739
def get_deltas_for_revisions(self, revisions):
741
return self._real_repository.get_deltas_for_revisions(revisions)
744
def get_revision_delta(self, revision_id):
746
return self._real_repository.get_revision_delta(revision_id)
1849
748
@needs_read_lock
1850
749
def revision_trees(self, revision_ids):
1997
843
self._ensure_real()
1998
844
return self._real_repository.has_signature_for_revision_id(revision_id)
846
def get_data_stream(self, revision_ids):
847
path = self.bzrdir._path_for_remote_call(self._client)
848
response, protocol = self._client.call_expecting_body(
849
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
850
if response == ('ok',):
851
return self._deserialise_stream(protocol)
852
elif (response == ('error', "Generic bzr smart protocol error: "
853
"bad request 'Repository.stream_knit_data_for_revisions'") or
854
response == ('error', "Generic bzr smart protocol error: "
855
"bad request u'Repository.stream_knit_data_for_revisions'")):
856
protocol.cancel_read_body()
858
return self._real_repository.get_data_stream(revision_ids)
860
raise errors.UnexpectedSmartServerResponse(response)
862
def _deserialise_stream(self, protocol):
863
buffer = StringIO(protocol.read_body_bytes())
864
reader = ContainerReader(buffer)
865
for record_names, read_bytes in reader.iter_records():
867
# These records should have only one name, and that name
868
# should be a one-element tuple.
869
[name_tuple] = record_names
871
raise errors.SmartProtocolError(
872
'Repository data stream had invalid record name %r'
874
yield name_tuple, read_bytes(None)
876
def insert_data_stream(self, stream):
878
self._real_repository.insert_data_stream(stream)
2000
880
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2001
881
self._ensure_real()
2002
882
return self._real_repository.item_keys_introduced_by(revision_ids,
2003
883
_files_pb=_files_pb)
2005
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2007
return self._real_repository._find_inconsistent_revision_parents(
885
def revision_graph_can_have_wrong_parents(self):
886
# The answer depends on the remote repo format.
888
return self._real_repository.revision_graph_can_have_wrong_parents()
890
def _find_inconsistent_revision_parents(self):
892
return self._real_repository._find_inconsistent_revision_parents()
2010
894
def _check_for_inconsistent_revision_parents(self):
2011
895
self._ensure_real()
2012
896
return self._real_repository._check_for_inconsistent_revision_parents()
2014
def _make_parents_provider(self, other=None):
2015
providers = [self._unstacked_provider]
2016
if other is not None:
2017
providers.insert(0, other)
2018
return graph.StackedParentsProvider(_LazyListJoin(
2019
providers, self._fallback_repositories))
2021
def _serialise_search_recipe(self, recipe):
2022
"""Serialise a graph search recipe.
2024
:param recipe: A search recipe (start, stop, count).
2025
:return: Serialised bytes.
2027
start_keys = ' '.join(recipe[1])
2028
stop_keys = ' '.join(recipe[2])
2029
count = str(recipe[3])
2030
return '\n'.join((start_keys, stop_keys, count))
2032
def _serialise_search_result(self, search_result):
2033
parts = search_result.get_network_struct()
2034
return '\n'.join(parts)
2037
path = self.bzrdir._path_for_remote_call(self._client)
2039
response = self._call('PackRepository.autopack', path)
2040
except errors.UnknownSmartMethod:
2042
self._real_repository._pack_collection.autopack()
2045
if response[0] != 'ok':
2046
raise errors.UnexpectedSmartServerResponse(response)
2049
class RemoteStreamSink(vf_repository.StreamSink):
2051
def _insert_real(self, stream, src_format, resume_tokens):
2052
self.target_repo._ensure_real()
2053
sink = self.target_repo._real_repository._get_sink()
2054
result = sink.insert_stream(stream, src_format, resume_tokens)
2056
self.target_repo.autopack()
2059
def insert_stream(self, stream, src_format, resume_tokens):
2060
target = self.target_repo
2061
target._unstacked_provider.missing_keys.clear()
2062
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2063
if target._lock_token:
2064
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2065
lock_args = (target._lock_token or '',)
2067
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2069
client = target._client
2070
medium = client._medium
2071
path = target.bzrdir._path_for_remote_call(client)
2072
# Probe for the verb to use with an empty stream before sending the
2073
# real stream to it. We do this both to avoid the risk of sending a
2074
# large request that is then rejected, and because we don't want to
2075
# implement a way to buffer, rewind, or restart the stream.
2077
for verb, required_version in candidate_calls:
2078
if medium._is_remote_before(required_version):
2081
# We've already done the probing (and set _is_remote_before) on
2082
# a previous insert.
2085
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2087
response = client.call_with_body_stream(
2088
(verb, path, '') + lock_args, byte_stream)
2089
except errors.UnknownSmartMethod:
2090
medium._remember_remote_is_before(required_version)
2096
return self._insert_real(stream, src_format, resume_tokens)
2097
self._last_inv_record = None
2098
self._last_substream = None
2099
if required_version < (1, 19):
2100
# Remote side doesn't support inventory deltas. Wrap the stream to
2101
# make sure we don't send any. If the stream contains inventory
2102
# deltas we'll interrupt the smart insert_stream request and
2104
stream = self._stop_stream_if_inventory_delta(stream)
2105
byte_stream = smart_repo._stream_to_byte_stream(
2107
resume_tokens = ' '.join(resume_tokens)
2108
response = client.call_with_body_stream(
2109
(verb, path, resume_tokens) + lock_args, byte_stream)
2110
if response[0][0] not in ('ok', 'missing-basis'):
2111
raise errors.UnexpectedSmartServerResponse(response)
2112
if self._last_substream is not None:
2113
# The stream included an inventory-delta record, but the remote
2114
# side isn't new enough to support them. So we need to send the
2115
# rest of the stream via VFS.
2116
self.target_repo.refresh_data()
2117
return self._resume_stream_with_vfs(response, src_format)
2118
if response[0][0] == 'missing-basis':
2119
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2120
resume_tokens = tokens
2121
return resume_tokens, set(missing_keys)
2123
self.target_repo.refresh_data()
2126
def _resume_stream_with_vfs(self, response, src_format):
2127
"""Resume sending a stream via VFS, first resending the record and
2128
substream that couldn't be sent via an insert_stream verb.
2130
if response[0][0] == 'missing-basis':
2131
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2132
# Ignore missing_keys, we haven't finished inserting yet
2135
def resume_substream():
2136
# Yield the substream that was interrupted.
2137
for record in self._last_substream:
2139
self._last_substream = None
2140
def resume_stream():
2141
# Finish sending the interrupted substream
2142
yield ('inventory-deltas', resume_substream())
2143
# Then simply continue sending the rest of the stream.
2144
for substream_kind, substream in self._last_stream:
2145
yield substream_kind, substream
2146
return self._insert_real(resume_stream(), src_format, tokens)
2148
def _stop_stream_if_inventory_delta(self, stream):
2149
"""Normally this just lets the original stream pass-through unchanged.
2151
However if any 'inventory-deltas' substream occurs it will stop
2152
streaming, and store the interrupted substream and stream in
2153
self._last_substream and self._last_stream so that the stream can be
2154
resumed by _resume_stream_with_vfs.
2157
stream_iter = iter(stream)
2158
for substream_kind, substream in stream_iter:
2159
if substream_kind == 'inventory-deltas':
2160
self._last_substream = substream
2161
self._last_stream = stream_iter
2164
yield substream_kind, substream
2167
class RemoteStreamSource(vf_repository.StreamSource):
2168
"""Stream data from a remote server."""
2170
def get_stream(self, search):
2171
if (self.from_repository._fallback_repositories and
2172
self.to_format._fetch_order == 'topological'):
2173
return self._real_stream(self.from_repository, search)
2176
repos = [self.from_repository]
2182
repos.extend(repo._fallback_repositories)
2183
sources.append(repo)
2184
return self.missing_parents_chain(search, sources)
2186
def get_stream_for_missing_keys(self, missing_keys):
2187
self.from_repository._ensure_real()
2188
real_repo = self.from_repository._real_repository
2189
real_source = real_repo._get_source(self.to_format)
2190
return real_source.get_stream_for_missing_keys(missing_keys)
2192
def _real_stream(self, repo, search):
2193
"""Get a stream for search from repo.
2195
This never called RemoteStreamSource.get_stream, and is a heler
2196
for RemoteStreamSource._get_stream to allow getting a stream
2197
reliably whether fallback back because of old servers or trying
2198
to stream from a non-RemoteRepository (which the stacked support
2201
source = repo._get_source(self.to_format)
2202
if isinstance(source, RemoteStreamSource):
2204
source = repo._real_repository._get_source(self.to_format)
2205
return source.get_stream(search)
2207
def _get_stream(self, repo, search):
2208
"""Core worker to get a stream from repo for search.
2210
This is used by both get_stream and the stacking support logic. It
2211
deliberately gets a stream for repo which does not need to be
2212
self.from_repository. In the event that repo is not Remote, or
2213
cannot do a smart stream, a fallback is made to the generic
2214
repository._get_stream() interface, via self._real_stream.
2216
In the event of stacking, streams from _get_stream will not
2217
contain all the data for search - this is normal (see get_stream).
2219
:param repo: A repository.
2220
:param search: A search.
2222
# Fallbacks may be non-smart
2223
if not isinstance(repo, RemoteRepository):
2224
return self._real_stream(repo, search)
2225
client = repo._client
2226
medium = client._medium
2227
path = repo.bzrdir._path_for_remote_call(client)
2228
search_bytes = repo._serialise_search_result(search)
2229
args = (path, self.to_format.network_name())
2231
('Repository.get_stream_1.19', (1, 19)),
2232
('Repository.get_stream', (1, 13))]
2235
for verb, version in candidate_verbs:
2236
if medium._is_remote_before(version):
2239
response = repo._call_with_body_bytes_expecting_body(
2240
verb, args, search_bytes)
2241
except errors.UnknownSmartMethod:
2242
medium._remember_remote_is_before(version)
2243
except errors.UnknownErrorFromSmartServer, e:
2244
if isinstance(search, graph.EverythingResult):
2245
error_verb = e.error_from_smart_server.error_verb
2246
if error_verb == 'BadSearch':
2247
# Pre-2.4 servers don't support this sort of search.
2248
# XXX: perhaps falling back to VFS on BadSearch is a
2249
# good idea in general? It might provide a little bit
2250
# of protection against client-side bugs.
2251
medium._remember_remote_is_before((2, 4))
2255
response_tuple, response_handler = response
2259
return self._real_stream(repo, search)
2260
if response_tuple[0] != 'ok':
2261
raise errors.UnexpectedSmartServerResponse(response_tuple)
2262
byte_stream = response_handler.read_streamed_body()
2263
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2264
self._record_counter)
2265
if src_format.network_name() != repo._format.network_name():
2266
raise AssertionError(
2267
"Mismatched RemoteRepository and stream src %r, %r" % (
2268
src_format.network_name(), repo._format.network_name()))
2271
def missing_parents_chain(self, search, sources):
2272
"""Chain multiple streams together to handle stacking.
2274
:param search: The overall search to satisfy with streams.
2275
:param sources: A list of Repository objects to query.
2277
self.from_serialiser = self.from_repository._format._serializer
2278
self.seen_revs = set()
2279
self.referenced_revs = set()
2280
# If there are heads in the search, or the key count is > 0, we are not
2282
while not search.is_empty() and len(sources) > 1:
2283
source = sources.pop(0)
2284
stream = self._get_stream(source, search)
2285
for kind, substream in stream:
2286
if kind != 'revisions':
2287
yield kind, substream
2289
yield kind, self.missing_parents_rev_handler(substream)
2290
search = search.refine(self.seen_revs, self.referenced_revs)
2291
self.seen_revs = set()
2292
self.referenced_revs = set()
2293
if not search.is_empty():
2294
for kind, stream in self._get_stream(sources[0], search):
2297
def missing_parents_rev_handler(self, substream):
2298
for content in substream:
2299
revision_bytes = content.get_bytes_as('fulltext')
2300
revision = self.from_serialiser.read_revision_from_string(
2302
self.seen_revs.add(content.key[-1])
2303
self.referenced_revs.update(revision.parent_ids)
898
def _make_parents_provider(self):
900
return self._real_repository._make_parents_provider()
2307
903
class RemoteBranchLockableFiles(LockableFiles):
2308
904
"""A 'LockableFiles' implementation that talks to a smart server.
2310
906
This is not a public interface class.
2323
919
self._dir_mode = None
2324
920
self._file_mode = None
923
"""'get' a remote path as per the LockableFiles interface.
925
:param path: the file to 'get'. If this is 'branch.conf', we do not
926
just retrieve a file, instead we ask the smart server to generate
927
a configuration for us - which is retrieved as an INI file.
929
if path == 'branch.conf':
930
path = self.bzrdir._path_for_remote_call(self._client)
931
response = self._client.call_expecting_body(
932
'Branch.get_config_file', path)
933
assert response[0][0] == 'ok', \
934
'unexpected response code %s' % (response[0],)
935
return StringIO(response[1].read_body_bytes())
938
return LockableFiles.get(self, path)
2327
941
class RemoteBranchFormat(branch.BranchFormat):
2329
def __init__(self, network_name=None):
2330
super(RemoteBranchFormat, self).__init__()
2331
self._matchingbzrdir = RemoteBzrDirFormat()
2332
self._matchingbzrdir.set_branch_format(self)
2333
self._custom_format = None
2334
self._network_name = network_name
2336
943
def __eq__(self, other):
2337
return (isinstance(other, RemoteBranchFormat) and
944
return (isinstance(other, RemoteBranchFormat) and
2338
945
self.__dict__ == other.__dict__)
2340
def _ensure_real(self):
2341
if self._custom_format is None:
2342
self._custom_format = branch.network_format_registry.get(
2345
947
def get_format_description(self):
2347
return 'Remote: ' + self._custom_format.get_format_description()
2349
def network_name(self):
2350
return self._network_name
2352
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2353
return a_bzrdir.open_branch(name=name,
2354
ignore_fallbacks=ignore_fallbacks)
2356
def _vfs_initialize(self, a_bzrdir, name):
2357
# Initialisation when using a local bzrdir object, or a non-vfs init
2358
# method is not available on the server.
2359
# self._custom_format is always set - the start of initialize ensures
2361
if isinstance(a_bzrdir, RemoteBzrDir):
2362
a_bzrdir._ensure_real()
2363
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2366
# We assume the bzrdir is parameterised; it may not be.
2367
result = self._custom_format.initialize(a_bzrdir, name)
2368
if (isinstance(a_bzrdir, RemoteBzrDir) and
2369
not isinstance(result, RemoteBranch)):
2370
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2374
def initialize(self, a_bzrdir, name=None, repository=None):
2375
# 1) get the network name to use.
2376
if self._custom_format:
2377
network_name = self._custom_format.network_name()
2379
# Select the current bzrlib default and ask for that.
2380
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2381
reference_format = reference_bzrdir_format.get_branch_format()
2382
self._custom_format = reference_format
2383
network_name = reference_format.network_name()
2384
# Being asked to create on a non RemoteBzrDir:
2385
if not isinstance(a_bzrdir, RemoteBzrDir):
2386
return self._vfs_initialize(a_bzrdir, name=name)
2387
medium = a_bzrdir._client._medium
2388
if medium._is_remote_before((1, 13)):
2389
return self._vfs_initialize(a_bzrdir, name=name)
2390
# Creating on a remote bzr dir.
2391
# 2) try direct creation via RPC
2392
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2393
if name is not None:
2394
# XXX JRV20100304: Support creating colocated branches
2395
raise errors.NoColocatedBranchSupport(self)
2396
verb = 'BzrDir.create_branch'
2398
response = a_bzrdir._call(verb, path, network_name)
2399
except errors.UnknownSmartMethod:
2400
# Fallback - use vfs methods
2401
medium._remember_remote_is_before((1, 13))
2402
return self._vfs_initialize(a_bzrdir, name=name)
2403
if response[0] != 'ok':
2404
raise errors.UnexpectedSmartServerResponse(response)
2405
# Turn the response into a RemoteRepository object.
2406
format = RemoteBranchFormat(network_name=response[1])
2407
repo_format = response_tuple_to_repo_format(response[3:])
2408
repo_path = response[2]
2409
if repository is not None:
2410
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2411
url_diff = urlutils.relative_url(repository.user_url,
2414
raise AssertionError(
2415
'repository.user_url %r does not match URL from server '
2416
'response (%r + %r)'
2417
% (repository.user_url, a_bzrdir.user_url, repo_path))
2418
remote_repo = repository
2421
repo_bzrdir = a_bzrdir
2423
repo_bzrdir = RemoteBzrDir(
2424
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2426
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2427
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2428
format=format, setup_stacking=False, name=name)
2429
# XXX: We know this is a new branch, so it must have revno 0, revid
2430
# NULL_REVISION. Creating the branch locked would make this be unable
2431
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2432
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2433
return remote_branch
2435
def make_tags(self, branch):
2437
return self._custom_format.make_tags(branch)
948
return 'Remote BZR Branch'
950
def get_format_string(self):
951
return 'Remote BZR Branch'
953
def open(self, a_bzrdir):
954
assert isinstance(a_bzrdir, RemoteBzrDir)
955
return a_bzrdir.open_branch()
957
def initialize(self, a_bzrdir):
958
assert isinstance(a_bzrdir, RemoteBzrDir)
959
return a_bzrdir.create_branch()
2439
961
def supports_tags(self):
2440
962
# Remote branches might support tags, but we won't know until we
2441
963
# access the real remote branch.
2443
return self._custom_format.supports_tags()
2445
def supports_stacking(self):
2447
return self._custom_format.supports_stacking()
2449
def supports_set_append_revisions_only(self):
2451
return self._custom_format.supports_set_append_revisions_only()
2453
def _use_default_local_heads_to_fetch(self):
2454
# If the branch format is a metadir format *and* its heads_to_fetch
2455
# implementation is not overridden vs the base class, we can use the
2456
# base class logic rather than use the heads_to_fetch RPC. This is
2457
# usually cheaper in terms of net round trips, as the last-revision and
2458
# tags info fetched is cached and would be fetched anyway.
2460
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2461
branch_class = self._custom_format._branch_class()
2462
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2463
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2467
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
967
class RemoteBranch(branch.Branch):
2468
968
"""Branch stored on a server accessed by HPSS RPC.
2470
970
At the moment most operations are mapped down to simple file operations.
2473
973
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2474
_client=None, format=None, setup_stacking=True, name=None):
2475
975
"""Create a RemoteBranch instance.
2477
977
:param real_branch: An optional local implementation of the branch
2478
978
format, usually accessing the data via the VFS.
2479
979
:param _client: Private parameter for testing.
2480
:param format: A RemoteBranchFormat object, None to create one
2481
automatically. If supplied it should have a network_name already
2483
:param setup_stacking: If True make an RPC call to determine the
2484
stacked (or not) status of the branch. If False assume the branch
2486
:param name: Colocated branch name
2488
981
# We intentionally don't call the parent class's __init__, because it
2489
982
# will try to assign to self.tags, which is a property in this subclass.
2490
983
# And the parent's __init__ doesn't do much anyway.
984
self._revision_id_to_revno_cache = None
985
self._revision_history_cache = None
2491
986
self.bzrdir = remote_bzrdir
2492
987
if _client is not None:
2493
988
self._client = _client
2495
self._client = remote_bzrdir._client
990
self._client = client._SmartClient(self.bzrdir._shared_medium)
2496
991
self.repository = remote_repository
2497
992
if real_branch is not None:
2498
993
self._real_branch = real_branch
2636
1055
self._ensure_real()
2637
1056
return self._real_branch.get_physical_lock_status()
2639
def get_stacked_on_url(self):
2640
"""Get the URL this branch is stacked against.
2642
:raises NotStacked: If the branch is not stacked.
2643
:raises UnstackableBranchFormat: If the branch does not support
2645
:raises UnstackableRepositoryFormat: If the repository does not support
2649
# there may not be a repository yet, so we can't use
2650
# self._translate_error, so we can't use self._call either.
2651
response = self._client.call('Branch.get_stacked_on_url',
2652
self._remote_path())
2653
except errors.ErrorFromSmartServer, err:
2654
# there may not be a repository yet, so we can't call through
2655
# its _translate_error
2656
_translate_error(err, branch=self)
2657
except errors.UnknownSmartMethod, err:
2659
return self._real_branch.get_stacked_on_url()
2660
if response[0] != 'ok':
2661
raise errors.UnexpectedSmartServerResponse(response)
2664
def set_stacked_on_url(self, url):
2665
branch.Branch.set_stacked_on_url(self, url)
2667
self._is_stacked = False
2669
self._is_stacked = True
2671
def _vfs_get_tags_bytes(self):
2673
return self._real_branch._get_tags_bytes()
2676
def _get_tags_bytes(self):
2677
if self._tags_bytes is None:
2678
self._tags_bytes = self._get_tags_bytes_via_hpss()
2679
return self._tags_bytes
2681
def _get_tags_bytes_via_hpss(self):
2682
medium = self._client._medium
2683
if medium._is_remote_before((1, 13)):
2684
return self._vfs_get_tags_bytes()
2686
response = self._call('Branch.get_tags_bytes', self._remote_path())
2687
except errors.UnknownSmartMethod:
2688
medium._remember_remote_is_before((1, 13))
2689
return self._vfs_get_tags_bytes()
2692
def _vfs_set_tags_bytes(self, bytes):
2694
return self._real_branch._set_tags_bytes(bytes)
2696
def _set_tags_bytes(self, bytes):
2697
if self.is_locked():
2698
self._tags_bytes = bytes
2699
medium = self._client._medium
2700
if medium._is_remote_before((1, 18)):
2701
self._vfs_set_tags_bytes(bytes)
2705
self._remote_path(), self._lock_token, self._repo_lock_token)
2706
response = self._call_with_body_bytes(
2707
'Branch.set_tags_bytes', args, bytes)
2708
except errors.UnknownSmartMethod:
2709
medium._remember_remote_is_before((1, 18))
2710
self._vfs_set_tags_bytes(bytes)
2712
1058
def lock_read(self):
2713
"""Lock the branch for read operations.
2715
:return: A bzrlib.lock.LogicalLockResult.
2717
self.repository.lock_read()
2718
1059
if not self._lock_mode:
2719
self._note_lock('r')
2720
1060
self._lock_mode = 'r'
2721
1061
self._lock_count = 1
2722
1062
if self._real_branch is not None:
2723
1063
self._real_branch.lock_read()
2725
1065
self._lock_count += 1
2726
return lock.LogicalLockResult(self.unlock)
2728
1067
def _remote_lock_write(self, token):
2729
1068
if token is None:
2730
1069
branch_token = repo_token = ''
2732
1071
branch_token = token
2733
repo_token = self.repository.lock_write().repository_token
1072
repo_token = self.repository.lock_write()
2734
1073
self.repository.unlock()
2735
err_context = {'token': token}
2737
response = self._call(
2738
'Branch.lock_write', self._remote_path(), branch_token,
2739
repo_token or '', **err_context)
2740
except errors.LockContention, e:
2741
# The LockContention from the server doesn't have any
2742
# information about the lock_url. We re-raise LockContention
2743
# with valid lock_url.
2744
raise errors.LockContention('(remote lock)',
2745
self.repository.base.split('.bzr/')[0])
2746
if response[0] != 'ok':
1074
path = self.bzrdir._path_for_remote_call(self._client)
1075
response = self._client.call('Branch.lock_write', path, branch_token,
1077
if response[0] == 'ok':
1078
ok, branch_token, repo_token = response
1079
return branch_token, repo_token
1080
elif response[0] == 'LockContention':
1081
raise errors.LockContention('(remote lock)')
1082
elif response[0] == 'TokenMismatch':
1083
raise errors.TokenMismatch(token, '(remote token)')
1084
elif response[0] == 'UnlockableTransport':
1085
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1086
elif response[0] == 'ReadOnlyError':
1087
raise errors.ReadOnlyError(self)
1088
elif response[0] == 'LockFailed':
1089
raise errors.LockFailed(response[1], response[2])
2747
1091
raise errors.UnexpectedSmartServerResponse(response)
2748
ok, branch_token, repo_token = response
2749
return branch_token, repo_token
2751
1093
def lock_write(self, token=None):
2752
1094
if not self._lock_mode:
2753
self._note_lock('w')
2754
# Lock the branch and repo in one remote call.
2755
1095
remote_tokens = self._remote_lock_write(token)
2756
1096
self._lock_token, self._repo_lock_token = remote_tokens
2757
if not self._lock_token:
2758
raise SmartProtocolError('Remote server did not return a token!')
2759
# Tell the self.repository object that it is locked.
2760
self.repository.lock_write(
2761
self._repo_lock_token, _skip_rpc=True)
1097
assert self._lock_token, 'Remote server did not return a token!'
1098
# TODO: We really, really, really don't want to call _ensure_real
1099
# here, but it's the easiest way to ensure coherency between the
1100
# state of the RemoteBranch and RemoteRepository objects and the
1101
# physical locks. If we don't materialise the real objects here,
1102
# then getting everything in the right state later is complex, so
1103
# for now we just do it the lazy way.
1104
# -- Andrew Bennetts, 2007-02-22.
2763
1106
if self._real_branch is not None:
2764
self._real_branch.lock_write(token=self._lock_token)
1107
self._real_branch.repository.lock_write(
1108
token=self._repo_lock_token)
1110
self._real_branch.lock_write(token=self._lock_token)
1112
self._real_branch.repository.unlock()
2765
1113
if token is not None:
2766
1114
self._leave_lock = True
1116
# XXX: this case seems to be unreachable; token cannot be None.
2768
1117
self._leave_lock = False
2769
1118
self._lock_mode = 'w'
2770
1119
self._lock_count = 1
2771
1120
elif self._lock_mode == 'r':
2772
raise errors.ReadOnlyError(self)
1121
raise errors.ReadOnlyTransaction
2774
1123
if token is not None:
2775
# A token was given to lock_write, and we're relocking, so
2776
# check that the given token actually matches the one we
1124
# A token was given to lock_write, and we're relocking, so check
1125
# that the given token actually matches the one we already have.
2778
1126
if token != self._lock_token:
2779
1127
raise errors.TokenMismatch(token, self._lock_token)
2780
1128
self._lock_count += 1
2781
# Re-lock the repository too.
2782
self.repository.lock_write(self._repo_lock_token)
2783
return BranchWriteLockResult(self.unlock, self._lock_token or None)
1129
return self._lock_token or None
2785
1131
def _unlock(self, branch_token, repo_token):
2786
err_context = {'token': str((branch_token, repo_token))}
2787
response = self._call(
2788
'Branch.unlock', self._remote_path(), branch_token,
2789
repo_token or '', **err_context)
1132
path = self.bzrdir._path_for_remote_call(self._client)
1133
response = self._client.call('Branch.unlock', path, branch_token,
2790
1135
if response == ('ok',):
2792
raise errors.UnexpectedSmartServerResponse(response)
1137
elif response[0] == 'TokenMismatch':
1138
raise errors.TokenMismatch(
1139
str((branch_token, repo_token)), '(remote tokens)')
1141
raise errors.UnexpectedSmartServerResponse(response)
2794
@only_raises(errors.LockNotHeld, errors.LockBroken)
2795
1143
def unlock(self):
2797
self._lock_count -= 1
2798
if not self._lock_count:
2799
self._clear_cached_state()
2800
mode = self._lock_mode
2801
self._lock_mode = None
2802
if self._real_branch is not None:
2803
if (not self._leave_lock and mode == 'w' and
2804
self._repo_lock_token):
2805
# If this RemoteBranch will remove the physical lock
2806
# for the repository, make sure the _real_branch
2807
# doesn't do it first. (Because the _real_branch's
2808
# repository is set to be the RemoteRepository.)
2809
self._real_branch.repository.leave_lock_in_place()
2810
self._real_branch.unlock()
2812
# Only write-locked branched need to make a remote method
2813
# call to perform the unlock.
2815
if not self._lock_token:
2816
raise AssertionError('Locked, but no token!')
2817
branch_token = self._lock_token
2818
repo_token = self._repo_lock_token
2819
self._lock_token = None
2820
self._repo_lock_token = None
2821
if not self._leave_lock:
2822
self._unlock(branch_token, repo_token)
2824
self.repository.unlock()
1144
self._lock_count -= 1
1145
if not self._lock_count:
1146
self._clear_cached_state()
1147
mode = self._lock_mode
1148
self._lock_mode = None
1149
if self._real_branch is not None:
1150
if (not self._leave_lock and mode == 'w' and
1151
self._repo_lock_token):
1152
# If this RemoteBranch will remove the physical lock for the
1153
# repository, make sure the _real_branch doesn't do it
1154
# first. (Because the _real_branch's repository is set to
1155
# be the RemoteRepository.)
1156
self._real_branch.repository.leave_lock_in_place()
1157
self._real_branch.unlock()
1159
# Only write-locked branched need to make a remote method call
1160
# to perfom the unlock.
1162
assert self._lock_token, 'Locked, but no token!'
1163
branch_token = self._lock_token
1164
repo_token = self._repo_lock_token
1165
self._lock_token = None
1166
self._repo_lock_token = None
1167
if not self._leave_lock:
1168
self._unlock(branch_token, repo_token)
2826
1170
def break_lock(self):
2827
1171
self._ensure_real()
2837
1181
raise NotImplementedError(self.dont_leave_lock_in_place)
2838
1182
self._leave_lock = False
2841
def get_rev_id(self, revno, history=None):
2843
return _mod_revision.NULL_REVISION
2844
last_revision_info = self.last_revision_info()
2845
ok, result = self.repository.get_rev_id_for_revno(
2846
revno, last_revision_info)
2849
missing_parent = result[1]
2850
# Either the revision named by the server is missing, or its parent
2851
# is. Call get_parent_map to determine which, so that we report a
2853
parent_map = self.repository.get_parent_map([missing_parent])
2854
if missing_parent in parent_map:
2855
missing_parent = parent_map[missing_parent]
2856
raise errors.RevisionNotPresent(missing_parent, self.repository)
2858
def _read_last_revision_info(self):
2859
response = self._call('Branch.last_revision_info', self._remote_path())
2860
if response[0] != 'ok':
2861
raise SmartProtocolError('unexpected response code %s' % (response,))
1184
def last_revision_info(self):
1185
"""See Branch.last_revision_info()."""
1186
path = self.bzrdir._path_for_remote_call(self._client)
1187
response = self._client.call('Branch.last_revision_info', path)
1188
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2862
1189
revno = int(response[1])
2863
1190
last_revision = response[2]
2864
1191
return (revno, last_revision)
2866
1193
def _gen_revision_history(self):
2867
1194
"""See Branch._gen_revision_history()."""
2868
if self._is_stacked:
2870
return self._real_branch._gen_revision_history()
2871
response_tuple, response_handler = self._call_expecting_body(
2872
'Branch.revision_history', self._remote_path())
2873
if response_tuple[0] != 'ok':
2874
raise errors.UnexpectedSmartServerResponse(response_tuple)
2875
result = response_handler.read_body_bytes().split('\x00')
1195
path = self.bzrdir._path_for_remote_call(self._client)
1196
response = self._client.call_expecting_body(
1197
'Branch.revision_history', path)
1198
assert response[0][0] == 'ok', ('unexpected response code %s'
1200
result = response[1].read_body_bytes().split('\x00')
2876
1201
if result == ['']:
2880
def _remote_path(self):
2881
return self.bzrdir._path_for_remote_call(self._client)
2883
def _set_last_revision_descendant(self, revision_id, other_branch,
2884
allow_diverged=False, allow_overwrite_descendant=False):
2885
# This performs additional work to meet the hook contract; while its
2886
# undesirable, we have to synthesise the revno to call the hook, and
2887
# not calling the hook is worse as it means changes can't be prevented.
2888
# Having calculated this though, we can't just call into
2889
# set_last_revision_info as a simple call, because there is a set_rh
2890
# hook that some folk may still be using.
2891
old_revno, old_revid = self.last_revision_info()
2892
history = self._lefthand_history(revision_id)
2893
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2894
err_context = {'other_branch': other_branch}
2895
response = self._call('Branch.set_last_revision_ex',
2896
self._remote_path(), self._lock_token, self._repo_lock_token,
2897
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2899
self._clear_cached_state()
2900
if len(response) != 3 and response[0] != 'ok':
2901
raise errors.UnexpectedSmartServerResponse(response)
2902
new_revno, new_revision_id = response[1:]
2903
self._last_revision_info_cache = new_revno, new_revision_id
2904
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2905
if self._real_branch is not None:
2906
cache = new_revno, new_revision_id
2907
self._real_branch._last_revision_info_cache = cache
2909
def _set_last_revision(self, revision_id):
2910
old_revno, old_revid = self.last_revision_info()
2911
# This performs additional work to meet the hook contract; while its
2912
# undesirable, we have to synthesise the revno to call the hook, and
2913
# not calling the hook is worse as it means changes can't be prevented.
2914
# Having calculated this though, we can't just call into
2915
# set_last_revision_info as a simple call, because there is a set_rh
2916
# hook that some folk may still be using.
2917
history = self._lefthand_history(revision_id)
2918
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2919
self._clear_cached_state()
2920
response = self._call('Branch.set_last_revision',
2921
self._remote_path(), self._lock_token, self._repo_lock_token,
2923
if response != ('ok',):
2924
raise errors.UnexpectedSmartServerResponse(response)
2925
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2927
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2928
1205
@needs_write_lock
2929
1206
def set_revision_history(self, rev_history):
2930
"""See Branch.set_revision_history."""
2931
self._set_revision_history(rev_history)
2934
def _set_revision_history(self, rev_history):
2935
1207
# Send just the tip revision of the history; the server will generate
2936
1208
# the full history from that. If the revision doesn't exist in this
2937
1209
# branch, NoSuchRevision will be raised.
1210
path = self.bzrdir._path_for_remote_call(self._client)
2938
1211
if rev_history == []:
2939
1212
rev_id = 'null:'
2941
1214
rev_id = rev_history[-1]
2942
self._set_last_revision(rev_id)
2943
for hook in branch.Branch.hooks['set_rh']:
2944
hook(self, rev_history)
1215
self._clear_cached_state()
1216
response = self._client.call('Branch.set_last_revision',
1217
path, self._lock_token, self._repo_lock_token, rev_id)
1218
if response[0] == 'NoSuchRevision':
1219
raise NoSuchRevision(self, rev_id)
1221
assert response == ('ok',), (
1222
'unexpected response code %r' % (response,))
2945
1223
self._cache_revision_history(rev_history)
2947
def _get_parent_location(self):
2948
medium = self._client._medium
2949
if medium._is_remote_before((1, 13)):
2950
return self._vfs_get_parent_location()
2952
response = self._call('Branch.get_parent', self._remote_path())
2953
except errors.UnknownSmartMethod:
2954
medium._remember_remote_is_before((1, 13))
2955
return self._vfs_get_parent_location()
2956
if len(response) != 1:
2957
raise errors.UnexpectedSmartServerResponse(response)
2958
parent_location = response[0]
2959
if parent_location == '':
2961
return parent_location
2963
def _vfs_get_parent_location(self):
2965
return self._real_branch._get_parent_location()
2967
def _set_parent_location(self, url):
2968
medium = self._client._medium
2969
if medium._is_remote_before((1, 15)):
2970
return self._vfs_set_parent_location(url)
2972
call_url = url or ''
2973
if type(call_url) is not str:
2974
raise AssertionError('url must be a str or None (%s)' % url)
2975
response = self._call('Branch.set_parent_location',
2976
self._remote_path(), self._lock_token, self._repo_lock_token,
2978
except errors.UnknownSmartMethod:
2979
medium._remember_remote_is_before((1, 15))
2980
return self._vfs_set_parent_location(url)
2982
raise errors.UnexpectedSmartServerResponse(response)
2984
def _vfs_set_parent_location(self, url):
2986
return self._real_branch._set_parent_location(url)
1225
def get_parent(self):
1227
return self._real_branch.get_parent()
1229
def set_parent(self, url):
1231
return self._real_branch.set_parent(url)
1233
def get_config(self):
1234
return RemoteBranchConfig(self)
1236
def sprout(self, to_bzrdir, revision_id=None):
1237
# Like Branch.sprout, except that it sprouts a branch in the default
1238
# format, because RemoteBranches can't be created at arbitrary URLs.
1239
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1240
# to_bzrdir.create_branch...
1242
result = self._real_branch._format.initialize(to_bzrdir)
1243
self.copy_content_into(result, revision_id=revision_id)
1244
result.set_parent(self.bzrdir.root_transport.base)
2988
1247
@needs_write_lock
2989
1248
def pull(self, source, overwrite=False, stop_revision=None,
2991
self._clear_cached_state_of_remote_branch_only()
1250
# FIXME: This asks the real branch to run the hooks, which means
1251
# they're called with the wrong target branch parameter.
1252
# The test suite specifically allows this at present but it should be
1253
# fixed. It should get a _override_hook_target branch,
1254
# as push does. -- mbp 20070405
2992
1255
self._ensure_real()
2993
return self._real_branch.pull(
1256
self._real_branch.pull(
2994
1257
source, overwrite=overwrite, stop_revision=stop_revision,
2995
_override_hook_target=self, **kwargs)
2997
1260
@needs_read_lock
2998
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
1261
def push(self, target, overwrite=False, stop_revision=None):
2999
1262
self._ensure_real()
3000
1263
return self._real_branch.push(
3001
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
1264
target, overwrite=overwrite, stop_revision=stop_revision,
3002
1265
_override_hook_source_branch=self)
3004
1267
def is_locked(self):
3005
1268
return self._lock_count >= 1
3008
def revision_id_to_revno(self, revision_id):
3010
return self._real_branch.revision_id_to_revno(revision_id)
3013
1270
def set_last_revision_info(self, revno, revision_id):
3014
# XXX: These should be returned by the set_last_revision_info verb
3015
old_revno, old_revid = self.last_revision_info()
3016
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3017
if not revision_id or not isinstance(revision_id, basestring):
3018
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3020
response = self._call('Branch.set_last_revision_info',
3021
self._remote_path(), self._lock_token, self._repo_lock_token,
3022
str(revno), revision_id)
3023
except errors.UnknownSmartMethod:
3025
self._clear_cached_state_of_remote_branch_only()
3026
self._real_branch.set_last_revision_info(revno, revision_id)
3027
self._last_revision_info_cache = revno, revision_id
3029
if response == ('ok',):
3030
self._clear_cached_state()
3031
self._last_revision_info_cache = revno, revision_id
3032
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3033
# Update the _real_branch's cache too.
3034
if self._real_branch is not None:
3035
cache = self._last_revision_info_cache
3036
self._real_branch._last_revision_info_cache = cache
3038
raise errors.UnexpectedSmartServerResponse(response)
1272
self._clear_cached_state()
1273
return self._real_branch.set_last_revision_info(revno, revision_id)
3041
1275
def generate_revision_history(self, revision_id, last_rev=None,
3042
1276
other_branch=None):
3043
medium = self._client._medium
3044
if not medium._is_remote_before((1, 6)):
3045
# Use a smart method for 1.6 and above servers
3047
self._set_last_revision_descendant(revision_id, other_branch,
3048
allow_diverged=True, allow_overwrite_descendant=True)
3050
except errors.UnknownSmartMethod:
3051
medium._remember_remote_is_before((1, 6))
3052
self._clear_cached_state_of_remote_branch_only()
3053
self._set_revision_history(self._lefthand_history(revision_id,
3054
last_rev=last_rev,other_branch=other_branch))
1278
return self._real_branch.generate_revision_history(
1279
revision_id, last_rev=last_rev, other_branch=other_branch)
1284
return self._real_branch.tags
3056
1286
def set_push_location(self, location):
3057
1287
self._ensure_real()
3058
1288
return self._real_branch.set_push_location(location)
3060
def heads_to_fetch(self):
3061
if self._format._use_default_local_heads_to_fetch():
3062
# We recognise this format, and its heads-to-fetch implementation
3063
# is the default one (tip + tags). In this case it's cheaper to
3064
# just use the default implementation rather than a special RPC as
3065
# the tip and tags data is cached.
3066
return branch.Branch.heads_to_fetch(self)
3067
medium = self._client._medium
3068
if medium._is_remote_before((2, 4)):
3069
return self._vfs_heads_to_fetch()
3071
return self._rpc_heads_to_fetch()
3072
except errors.UnknownSmartMethod:
3073
medium._remember_remote_is_before((2, 4))
3074
return self._vfs_heads_to_fetch()
3076
def _rpc_heads_to_fetch(self):
3077
response = self._call('Branch.heads_to_fetch', self._remote_path())
3078
if len(response) != 2:
3079
raise errors.UnexpectedSmartServerResponse(response)
3080
must_fetch, if_present_fetch = response
3081
return set(must_fetch), set(if_present_fetch)
3083
def _vfs_heads_to_fetch(self):
1290
def update_revisions(self, other, stop_revision=None, overwrite=False):
3084
1291
self._ensure_real()
3085
return self._real_branch.heads_to_fetch()
3088
class RemoteConfig(object):
3089
"""A Config that reads and writes from smart verbs.
3091
It is a low-level object that considers config data to be name/value pairs
3092
that may be associated with a section. Assigning meaning to the these
3093
values is done at higher levels like bzrlib.config.TreeConfig.
3096
def get_option(self, name, section=None, default=None):
3097
"""Return the value associated with a named option.
3099
:param name: The name of the value
3100
:param section: The section the option is in (if any)
3101
:param default: The value to return if the value is not set
3102
:return: The value or default value
3105
configobj = self._get_configobj()
3108
section_obj = configobj
3111
section_obj = configobj[section]
3114
if section_obj is None:
3117
value = section_obj.get(name, default)
3118
except errors.UnknownSmartMethod:
3119
value = self._vfs_get_option(name, section, default)
3120
for hook in config.OldConfigHooks['get']:
3121
hook(self, name, value)
3124
def _response_to_configobj(self, response):
3125
if len(response[0]) and response[0][0] != 'ok':
3126
raise errors.UnexpectedSmartServerResponse(response)
3127
lines = response[1].read_body_bytes().splitlines()
3128
conf = config.ConfigObj(lines, encoding='utf-8')
3129
for hook in config.OldConfigHooks['load']:
3134
class RemoteBranchConfig(RemoteConfig):
3135
"""A RemoteConfig for Branches."""
3137
def __init__(self, branch):
3138
self._branch = branch
3140
def _get_configobj(self):
3141
path = self._branch._remote_path()
3142
response = self._branch._client.call_expecting_body(
3143
'Branch.get_config_file', path)
3144
return self._response_to_configobj(response)
3146
def set_option(self, value, name, section=None):
3147
"""Set the value associated with a named option.
3149
:param value: The value to set
3150
:param name: The name of the value to set
3151
:param section: The section the option is in (if any)
3153
medium = self._branch._client._medium
3154
if medium._is_remote_before((1, 14)):
3155
return self._vfs_set_option(value, name, section)
3156
if isinstance(value, dict):
3157
if medium._is_remote_before((2, 2)):
3158
return self._vfs_set_option(value, name, section)
3159
return self._set_config_option_dict(value, name, section)
3161
return self._set_config_option(value, name, section)
3163
def _set_config_option(self, value, name, section):
3165
path = self._branch._remote_path()
3166
response = self._branch._client.call('Branch.set_config_option',
3167
path, self._branch._lock_token, self._branch._repo_lock_token,
3168
value.encode('utf8'), name, section or '')
3169
except errors.UnknownSmartMethod:
3170
medium = self._branch._client._medium
3171
medium._remember_remote_is_before((1, 14))
3172
return self._vfs_set_option(value, name, section)
3174
raise errors.UnexpectedSmartServerResponse(response)
3176
def _serialize_option_dict(self, option_dict):
3178
for key, value in option_dict.items():
3179
if isinstance(key, unicode):
3180
key = key.encode('utf8')
3181
if isinstance(value, unicode):
3182
value = value.encode('utf8')
3183
utf8_dict[key] = value
3184
return bencode.bencode(utf8_dict)
3186
def _set_config_option_dict(self, value, name, section):
3188
path = self._branch._remote_path()
3189
serialised_dict = self._serialize_option_dict(value)
3190
response = self._branch._client.call(
3191
'Branch.set_config_option_dict',
3192
path, self._branch._lock_token, self._branch._repo_lock_token,
3193
serialised_dict, name, section or '')
3194
except errors.UnknownSmartMethod:
3195
medium = self._branch._client._medium
3196
medium._remember_remote_is_before((2, 2))
3197
return self._vfs_set_option(value, name, section)
3199
raise errors.UnexpectedSmartServerResponse(response)
3201
def _real_object(self):
3202
self._branch._ensure_real()
3203
return self._branch._real_branch
3205
def _vfs_set_option(self, value, name, section=None):
3206
return self._real_object()._get_config().set_option(
3207
value, name, section)
3210
class RemoteBzrDirConfig(RemoteConfig):
3211
"""A RemoteConfig for BzrDirs."""
3213
def __init__(self, bzrdir):
3214
self._bzrdir = bzrdir
3216
def _get_configobj(self):
3217
medium = self._bzrdir._client._medium
3218
verb = 'BzrDir.get_config_file'
3219
if medium._is_remote_before((1, 15)):
3220
raise errors.UnknownSmartMethod(verb)
3221
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3222
response = self._bzrdir._call_expecting_body(
3224
return self._response_to_configobj(response)
3226
def _vfs_get_option(self, name, section, default):
3227
return self._real_object()._get_config().get_option(
3228
name, section, default)
3230
def set_option(self, value, name, section=None):
3231
"""Set the value associated with a named option.
3233
:param value: The value to set
3234
:param name: The name of the value to set
3235
:param section: The section the option is in (if any)
3237
return self._real_object()._get_config().set_option(
3238
value, name, section)
3240
def _real_object(self):
3241
self._bzrdir._ensure_real()
3242
return self._bzrdir._real_bzrdir
1292
return self._real_branch.update_revisions(
1293
other, stop_revision=stop_revision, overwrite=overwrite)
1296
class RemoteBranchConfig(BranchConfig):
1299
self.branch._ensure_real()
1300
return self.branch._real_branch.get_config().username()
1302
def _get_branch_data_config(self):
1303
self.branch._ensure_real()
1304
if self._branch_data_config is None:
1305
self._branch_data_config = TreeConfig(self.branch._real_branch)
1306
return self._branch_data_config
3246
1309
def _extract_tar(tar, to_dir):
3251
1314
for tarinfo in tar:
3252
1315
tar.extract(tarinfo, to_dir)
3255
def _translate_error(err, **context):
3256
"""Translate an ErrorFromSmartServer into a more useful error.
3258
Possible context keys:
3266
If the error from the server doesn't match a known pattern, then
3267
UnknownErrorFromSmartServer is raised.
3271
return context[name]
3272
except KeyError, key_err:
3273
mutter('Missing key %r in context %r', key_err.args[0], context)
3276
"""Get the path from the context if present, otherwise use first error
3280
return context['path']
3281
except KeyError, key_err:
3283
return err.error_args[0]
3284
except IndexError, idx_err:
3286
'Missing key %r in context %r', key_err.args[0], context)
3289
if err.error_verb == 'NoSuchRevision':
3290
raise NoSuchRevision(find('branch'), err.error_args[0])
3291
elif err.error_verb == 'nosuchrevision':
3292
raise NoSuchRevision(find('repository'), err.error_args[0])
3293
elif err.error_verb == 'nobranch':
3294
if len(err.error_args) >= 1:
3295
extra = err.error_args[0]
3298
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3300
elif err.error_verb == 'norepository':
3301
raise errors.NoRepositoryPresent(find('bzrdir'))
3302
elif err.error_verb == 'UnlockableTransport':
3303
raise errors.UnlockableTransport(find('bzrdir').root_transport)
3304
elif err.error_verb == 'TokenMismatch':
3305
raise errors.TokenMismatch(find('token'), '(remote token)')
3306
elif err.error_verb == 'Diverged':
3307
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3308
elif err.error_verb == 'NotStacked':
3309
raise errors.NotStacked(branch=find('branch'))
3310
elif err.error_verb == 'PermissionDenied':
3312
if len(err.error_args) >= 2:
3313
extra = err.error_args[1]
3316
raise errors.PermissionDenied(path, extra=extra)
3317
elif err.error_verb == 'ReadError':
3319
raise errors.ReadError(path)
3320
elif err.error_verb == 'NoSuchFile':
3322
raise errors.NoSuchFile(path)
3323
_translate_error_without_context(err)
3326
def _translate_error_without_context(err):
3327
"""Translate any ErrorFromSmartServer values that don't require context"""
3328
if err.error_verb == 'IncompatibleRepositories':
3329
raise errors.IncompatibleRepositories(err.error_args[0],
3330
err.error_args[1], err.error_args[2])
3331
elif err.error_verb == 'LockContention':
3332
raise errors.LockContention('(remote lock)')
3333
elif err.error_verb == 'LockFailed':
3334
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3335
elif err.error_verb == 'TipChangeRejected':
3336
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3337
elif err.error_verb == 'UnstackableBranchFormat':
3338
raise errors.UnstackableBranchFormat(*err.error_args)
3339
elif err.error_verb == 'UnstackableRepositoryFormat':
3340
raise errors.UnstackableRepositoryFormat(*err.error_args)
3341
elif err.error_verb == 'FileExists':
3342
raise errors.FileExists(err.error_args[0])
3343
elif err.error_verb == 'DirectoryNotEmpty':
3344
raise errors.DirectoryNotEmpty(err.error_args[0])
3345
elif err.error_verb == 'ShortReadvError':
3346
args = err.error_args
3347
raise errors.ShortReadvError(
3348
args[0], int(args[1]), int(args[2]), int(args[3]))
3349
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3350
encoding = str(err.error_args[0]) # encoding must always be a string
3351
val = err.error_args[1]
3352
start = int(err.error_args[2])
3353
end = int(err.error_args[3])
3354
reason = str(err.error_args[4]) # reason must always be a string
3355
if val.startswith('u:'):
3356
val = val[2:].decode('utf-8')
3357
elif val.startswith('s:'):
3358
val = val[2:].decode('base64')
3359
if err.error_verb == 'UnicodeDecodeError':
3360
raise UnicodeDecodeError(encoding, val, start, end, reason)
3361
elif err.error_verb == 'UnicodeEncodeError':
3362
raise UnicodeEncodeError(encoding, val, start, end, reason)
3363
elif err.error_verb == 'ReadOnlyError':
3364
raise errors.TransportNotPossible('readonly transport')
3365
elif err.error_verb == 'MemoryError':
3366
raise errors.BzrError("remote server out of memory\n"
3367
"Retry non-remotely, or contact the server admin for details.")
3368
raise errors.UnknownErrorFromSmartServer(err)