1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
bzrdir as _mod_bzrdir,
30
repository as _mod_repository,
31
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
37
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
38
from bzrlib.errors import (
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.smart import client, vfs, repository as smart_repo
44
from bzrlib.smart.client import _SmartClient
45
from bzrlib.revision import NULL_REVISION
46
from bzrlib.repository import RepositoryWriteLockResult
47
from bzrlib.trace import mutter, note, warning
50
class _RpcHelper(object):
51
"""Mixin class that helps with issuing RPCs."""
53
def _call(self, method, *args, **err_context):
55
return self._client.call(method, *args)
56
except errors.ErrorFromSmartServer, err:
57
self._translate_error(err, **err_context)
59
def _call_expecting_body(self, method, *args, **err_context):
61
return self._client.call_expecting_body(method, *args)
62
except errors.ErrorFromSmartServer, err:
63
self._translate_error(err, **err_context)
65
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
67
return self._client.call_with_body_bytes(method, args, body_bytes)
68
except errors.ErrorFromSmartServer, err:
69
self._translate_error(err, **err_context)
71
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
74
return self._client.call_with_body_bytes_expecting_body(
75
method, args, body_bytes)
76
except errors.ErrorFromSmartServer, err:
77
self._translate_error(err, **err_context)
80
def response_tuple_to_repo_format(response):
81
"""Convert a response tuple describing a repository format to a format."""
82
format = RemoteRepositoryFormat()
83
format._rich_root_data = (response[0] == 'yes')
84
format._supports_tree_reference = (response[1] == 'yes')
85
format._supports_external_lookups = (response[2] == 'yes')
86
format._network_name = response[3]
90
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
91
# does not have to be imported unless a remote format is involved.
93
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
94
"""Format representing bzrdirs accessed via a smart server"""
96
supports_workingtrees = False
99
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
100
# XXX: It's a bit ugly that the network name is here, because we'd
101
# like to believe that format objects are stateless or at least
102
# immutable, However, we do at least avoid mutating the name after
103
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
104
self._network_name = None
107
return "%s(_network_name=%r)" % (self.__class__.__name__,
110
def get_format_description(self):
111
if self._network_name:
112
real_format = controldir.network_format_registry.get(self._network_name)
113
return 'Remote: ' + real_format.get_format_description()
114
return 'bzr remote bzrdir'
116
def get_format_string(self):
117
raise NotImplementedError(self.get_format_string)
119
def network_name(self):
120
if self._network_name:
121
return self._network_name
123
raise AssertionError("No network name set.")
125
def initialize_on_transport(self, transport):
127
# hand off the request to the smart server
128
client_medium = transport.get_smart_medium()
129
except errors.NoSmartMedium:
130
# TODO: lookup the local format from a server hint.
131
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
132
return local_dir_format.initialize_on_transport(transport)
133
client = _SmartClient(client_medium)
134
path = client.remote_path_from_transport(transport)
136
response = client.call('BzrDirFormat.initialize', path)
137
except errors.ErrorFromSmartServer, err:
138
_translate_error(err, path=path)
139
if response[0] != 'ok':
140
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
141
format = RemoteBzrDirFormat()
142
self._supply_sub_formats_to(format)
143
return RemoteBzrDir(transport, format)
145
def parse_NoneTrueFalse(self, arg):
152
raise AssertionError("invalid arg %r" % arg)
154
def _serialize_NoneTrueFalse(self, arg):
161
def _serialize_NoneString(self, arg):
164
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
165
create_prefix=False, force_new_repo=False, stacked_on=None,
166
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
169
# hand off the request to the smart server
170
client_medium = transport.get_smart_medium()
171
except errors.NoSmartMedium:
174
# Decline to open it if the server doesn't support our required
175
# version (3) so that the VFS-based transport will do it.
176
if client_medium.should_probe():
178
server_version = client_medium.protocol_version()
179
if server_version != '2':
183
except errors.SmartProtocolError:
184
# Apparently there's no usable smart server there, even though
185
# the medium supports the smart protocol.
190
client = _SmartClient(client_medium)
191
path = client.remote_path_from_transport(transport)
192
if client_medium._is_remote_before((1, 16)):
195
# TODO: lookup the local format from a server hint.
196
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
197
self._supply_sub_formats_to(local_dir_format)
198
return local_dir_format.initialize_on_transport_ex(transport,
199
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
200
force_new_repo=force_new_repo, stacked_on=stacked_on,
201
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
202
make_working_trees=make_working_trees, shared_repo=shared_repo,
204
return self._initialize_on_transport_ex_rpc(client, path, transport,
205
use_existing_dir, create_prefix, force_new_repo, stacked_on,
206
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
208
def _initialize_on_transport_ex_rpc(self, client, path, transport,
209
use_existing_dir, create_prefix, force_new_repo, stacked_on,
210
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
212
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
213
args.append(self._serialize_NoneTrueFalse(create_prefix))
214
args.append(self._serialize_NoneTrueFalse(force_new_repo))
215
args.append(self._serialize_NoneString(stacked_on))
216
# stack_on_pwd is often/usually our transport
219
stack_on_pwd = transport.relpath(stack_on_pwd)
222
except errors.PathNotChild:
224
args.append(self._serialize_NoneString(stack_on_pwd))
225
args.append(self._serialize_NoneString(repo_format_name))
226
args.append(self._serialize_NoneTrueFalse(make_working_trees))
227
args.append(self._serialize_NoneTrueFalse(shared_repo))
228
request_network_name = self._network_name or \
229
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
231
response = client.call('BzrDirFormat.initialize_ex_1.16',
232
request_network_name, path, *args)
233
except errors.UnknownSmartMethod:
234
client._medium._remember_remote_is_before((1,16))
235
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
236
self._supply_sub_formats_to(local_dir_format)
237
return local_dir_format.initialize_on_transport_ex(transport,
238
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
239
force_new_repo=force_new_repo, stacked_on=stacked_on,
240
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
241
make_working_trees=make_working_trees, shared_repo=shared_repo,
243
except errors.ErrorFromSmartServer, err:
244
_translate_error(err, path=path)
245
repo_path = response[0]
246
bzrdir_name = response[6]
247
require_stacking = response[7]
248
require_stacking = self.parse_NoneTrueFalse(require_stacking)
249
format = RemoteBzrDirFormat()
250
format._network_name = bzrdir_name
251
self._supply_sub_formats_to(format)
252
bzrdir = RemoteBzrDir(transport, format, _client=client)
254
repo_format = response_tuple_to_repo_format(response[1:])
258
repo_bzrdir_format = RemoteBzrDirFormat()
259
repo_bzrdir_format._network_name = response[5]
260
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
264
final_stack = response[8] or None
265
final_stack_pwd = response[9] or None
267
final_stack_pwd = urlutils.join(
268
transport.base, final_stack_pwd)
269
remote_repo = RemoteRepository(repo_bzr, repo_format)
270
if len(response) > 10:
271
# Updated server verb that locks remotely.
272
repo_lock_token = response[10] or None
273
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
275
remote_repo.dont_leave_lock_in_place()
277
remote_repo.lock_write()
278
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
279
final_stack_pwd, require_stacking)
280
policy.acquire_repository()
284
bzrdir._format.set_branch_format(self.get_branch_format())
286
# The repo has already been created, but we need to make sure that
287
# we'll make a stackable branch.
288
bzrdir._format.require_stacking(_skip_repo=True)
289
return remote_repo, bzrdir, require_stacking, policy
291
def _open(self, transport):
292
return RemoteBzrDir(transport, self)
294
def __eq__(self, other):
295
if not isinstance(other, RemoteBzrDirFormat):
297
return self.get_format_description() == other.get_format_description()
299
def __return_repository_format(self):
300
# Always return a RemoteRepositoryFormat object, but if a specific bzr
301
# repository format has been asked for, tell the RemoteRepositoryFormat
302
# that it should use that for init() etc.
303
result = RemoteRepositoryFormat()
304
custom_format = getattr(self, '_repository_format', None)
306
if isinstance(custom_format, RemoteRepositoryFormat):
309
# We will use the custom format to create repositories over the
310
# wire; expose its details like rich_root_data for code to
312
result._custom_format = custom_format
315
def get_branch_format(self):
316
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
317
if not isinstance(result, RemoteBranchFormat):
318
new_result = RemoteBranchFormat()
319
new_result._custom_format = result
321
self.set_branch_format(new_result)
325
repository_format = property(__return_repository_format,
326
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
329
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
330
"""Control directory on a remote server, accessed via bzr:// or similar."""
332
def __init__(self, transport, format, _client=None, _force_probe=False):
333
"""Construct a RemoteBzrDir.
335
:param _client: Private parameter for testing. Disables probing and the
336
use of a real bzrdir.
338
_mod_bzrdir.BzrDir.__init__(self, transport, format)
339
# this object holds a delegated bzrdir that uses file-level operations
340
# to talk to the other side
341
self._real_bzrdir = None
342
self._has_working_tree = None
343
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
344
# create_branch for details.
345
self._next_open_branch_result = None
348
medium = transport.get_smart_medium()
349
self._client = client._SmartClient(medium)
351
self._client = _client
358
return '%s(%r)' % (self.__class__.__name__, self._client)
360
def _probe_bzrdir(self):
361
medium = self._client._medium
362
path = self._path_for_remote_call(self._client)
363
if medium._is_remote_before((2, 1)):
367
self._rpc_open_2_1(path)
369
except errors.UnknownSmartMethod:
370
medium._remember_remote_is_before((2, 1))
373
def _rpc_open_2_1(self, path):
374
response = self._call('BzrDir.open_2.1', path)
375
if response == ('no',):
376
raise errors.NotBranchError(path=self.root_transport.base)
377
elif response[0] == 'yes':
378
if response[1] == 'yes':
379
self._has_working_tree = True
380
elif response[1] == 'no':
381
self._has_working_tree = False
383
raise errors.UnexpectedSmartServerResponse(response)
385
raise errors.UnexpectedSmartServerResponse(response)
387
def _rpc_open(self, path):
388
response = self._call('BzrDir.open', path)
389
if response not in [('yes',), ('no',)]:
390
raise errors.UnexpectedSmartServerResponse(response)
391
if response == ('no',):
392
raise errors.NotBranchError(path=self.root_transport.base)
394
def _ensure_real(self):
395
"""Ensure that there is a _real_bzrdir set.
397
Used before calls to self._real_bzrdir.
399
if not self._real_bzrdir:
400
if 'hpssvfs' in debug.debug_flags:
402
warning('VFS BzrDir access triggered\n%s',
403
''.join(traceback.format_stack()))
404
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
405
self.root_transport, _server_formats=False)
406
self._format._network_name = \
407
self._real_bzrdir._format.network_name()
409
def _translate_error(self, err, **context):
410
_translate_error(err, bzrdir=self, **context)
412
def break_lock(self):
413
# Prevent aliasing problems in the next_open_branch_result cache.
414
# See create_branch for rationale.
415
self._next_open_branch_result = None
416
return _mod_bzrdir.BzrDir.break_lock(self)
418
def _vfs_cloning_metadir(self, require_stacking=False):
420
return self._real_bzrdir.cloning_metadir(
421
require_stacking=require_stacking)
423
def cloning_metadir(self, require_stacking=False):
424
medium = self._client._medium
425
if medium._is_remote_before((1, 13)):
426
return self._vfs_cloning_metadir(require_stacking=require_stacking)
427
verb = 'BzrDir.cloning_metadir'
432
path = self._path_for_remote_call(self._client)
434
response = self._call(verb, path, stacking)
435
except errors.UnknownSmartMethod:
436
medium._remember_remote_is_before((1, 13))
437
return self._vfs_cloning_metadir(require_stacking=require_stacking)
438
except errors.UnknownErrorFromSmartServer, err:
439
if err.error_tuple != ('BranchReference',):
441
# We need to resolve the branch reference to determine the
442
# cloning_metadir. This causes unnecessary RPCs to open the
443
# referenced branch (and bzrdir, etc) but only when the caller
444
# didn't already resolve the branch reference.
445
referenced_branch = self.open_branch()
446
return referenced_branch.bzrdir.cloning_metadir()
447
if len(response) != 3:
448
raise errors.UnexpectedSmartServerResponse(response)
449
control_name, repo_name, branch_info = response
450
if len(branch_info) != 2:
451
raise errors.UnexpectedSmartServerResponse(response)
452
branch_ref, branch_name = branch_info
453
format = controldir.network_format_registry.get(control_name)
455
format.repository_format = _mod_repository.network_format_registry.get(
457
if branch_ref == 'ref':
458
# XXX: we need possible_transports here to avoid reopening the
459
# connection to the referenced location
460
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
461
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
462
format.set_branch_format(branch_format)
463
elif branch_ref == 'branch':
465
format.set_branch_format(
466
branch.network_format_registry.get(branch_name))
468
raise errors.UnexpectedSmartServerResponse(response)
471
def create_repository(self, shared=False):
472
# as per meta1 formats - just delegate to the format object which may
474
result = self._format.repository_format.initialize(self, shared)
475
if not isinstance(result, RemoteRepository):
476
return self.open_repository()
480
def destroy_repository(self):
481
"""See BzrDir.destroy_repository"""
483
self._real_bzrdir.destroy_repository()
485
def create_branch(self, name=None, repository=None):
486
# as per meta1 formats - just delegate to the format object which may
488
real_branch = self._format.get_branch_format().initialize(self,
489
name=name, repository=repository)
490
if not isinstance(real_branch, RemoteBranch):
491
if not isinstance(repository, RemoteRepository):
492
raise AssertionError(
493
'need a RemoteRepository to use with RemoteBranch, got %r'
495
result = RemoteBranch(self, repository, real_branch, name=name)
498
# BzrDir.clone_on_transport() uses the result of create_branch but does
499
# not return it to its callers; we save approximately 8% of our round
500
# trips by handing the branch we created back to the first caller to
501
# open_branch rather than probing anew. Long term we need a API in
502
# bzrdir that doesn't discard result objects (like result_branch).
504
self._next_open_branch_result = result
507
def destroy_branch(self, name=None):
508
"""See BzrDir.destroy_branch"""
510
self._real_bzrdir.destroy_branch(name=name)
511
self._next_open_branch_result = None
513
def create_workingtree(self, revision_id=None, from_branch=None,
514
accelerator_tree=None, hardlink=False):
515
raise errors.NotLocalUrl(self.transport.base)
517
def find_branch_format(self, name=None):
518
"""Find the branch 'format' for this bzrdir.
520
This might be a synthetic object for e.g. RemoteBranch and SVN.
522
b = self.open_branch(name=name)
525
def get_branch_reference(self, name=None):
526
"""See BzrDir.get_branch_reference()."""
528
# XXX JRV20100304: Support opening colocated branches
529
raise errors.NoColocatedBranchSupport(self)
530
response = self._get_branch_reference()
531
if response[0] == 'ref':
536
def _get_branch_reference(self):
537
path = self._path_for_remote_call(self._client)
538
medium = self._client._medium
540
('BzrDir.open_branchV3', (2, 1)),
541
('BzrDir.open_branchV2', (1, 13)),
542
('BzrDir.open_branch', None),
544
for verb, required_version in candidate_calls:
545
if required_version and medium._is_remote_before(required_version):
548
response = self._call(verb, path)
549
except errors.UnknownSmartMethod:
550
if required_version is None:
552
medium._remember_remote_is_before(required_version)
555
if verb == 'BzrDir.open_branch':
556
if response[0] != 'ok':
557
raise errors.UnexpectedSmartServerResponse(response)
558
if response[1] != '':
559
return ('ref', response[1])
561
return ('branch', '')
562
if response[0] not in ('ref', 'branch'):
563
raise errors.UnexpectedSmartServerResponse(response)
566
def _get_tree_branch(self, name=None):
567
"""See BzrDir._get_tree_branch()."""
568
return None, self.open_branch(name=name)
570
def open_branch(self, name=None, unsupported=False,
571
ignore_fallbacks=False):
573
raise NotImplementedError('unsupported flag support not implemented yet.')
574
if self._next_open_branch_result is not None:
575
# See create_branch for details.
576
result = self._next_open_branch_result
577
self._next_open_branch_result = None
579
response = self._get_branch_reference()
580
if response[0] == 'ref':
581
# a branch reference, use the existing BranchReference logic.
582
format = BranchReferenceFormat()
583
return format.open(self, name=name, _found=True,
584
location=response[1], ignore_fallbacks=ignore_fallbacks)
585
branch_format_name = response[1]
586
if not branch_format_name:
587
branch_format_name = None
588
format = RemoteBranchFormat(network_name=branch_format_name)
589
return RemoteBranch(self, self.find_repository(), format=format,
590
setup_stacking=not ignore_fallbacks, name=name)
592
def _open_repo_v1(self, path):
593
verb = 'BzrDir.find_repository'
594
response = self._call(verb, path)
595
if response[0] != 'ok':
596
raise errors.UnexpectedSmartServerResponse(response)
597
# servers that only support the v1 method don't support external
600
repo = self._real_bzrdir.open_repository()
601
response = response + ('no', repo._format.network_name())
602
return response, repo
604
def _open_repo_v2(self, path):
605
verb = 'BzrDir.find_repositoryV2'
606
response = self._call(verb, path)
607
if response[0] != 'ok':
608
raise errors.UnexpectedSmartServerResponse(response)
610
repo = self._real_bzrdir.open_repository()
611
response = response + (repo._format.network_name(),)
612
return response, repo
614
def _open_repo_v3(self, path):
615
verb = 'BzrDir.find_repositoryV3'
616
medium = self._client._medium
617
if medium._is_remote_before((1, 13)):
618
raise errors.UnknownSmartMethod(verb)
620
response = self._call(verb, path)
621
except errors.UnknownSmartMethod:
622
medium._remember_remote_is_before((1, 13))
624
if response[0] != 'ok':
625
raise errors.UnexpectedSmartServerResponse(response)
626
return response, None
628
def open_repository(self):
629
path = self._path_for_remote_call(self._client)
631
for probe in [self._open_repo_v3, self._open_repo_v2,
634
response, real_repo = probe(path)
636
except errors.UnknownSmartMethod:
639
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
640
if response[0] != 'ok':
641
raise errors.UnexpectedSmartServerResponse(response)
642
if len(response) != 6:
643
raise SmartProtocolError('incorrect response length %s' % (response,))
644
if response[1] == '':
645
# repo is at this dir.
646
format = response_tuple_to_repo_format(response[2:])
647
# Used to support creating a real format instance when needed.
648
format._creating_bzrdir = self
649
remote_repo = RemoteRepository(self, format)
650
format._creating_repo = remote_repo
651
if real_repo is not None:
652
remote_repo._set_real_repository(real_repo)
655
raise errors.NoRepositoryPresent(self)
657
def has_workingtree(self):
658
if self._has_working_tree is None:
660
self._has_working_tree = self._real_bzrdir.has_workingtree()
661
return self._has_working_tree
663
def open_workingtree(self, recommend_upgrade=True):
664
if self.has_workingtree():
665
raise errors.NotLocalUrl(self.root_transport)
667
raise errors.NoWorkingTree(self.root_transport.base)
669
def _path_for_remote_call(self, client):
670
"""Return the path to be used for this bzrdir in a remote call."""
671
return client.remote_path_from_transport(self.root_transport)
673
def get_branch_transport(self, branch_format, name=None):
675
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
677
def get_repository_transport(self, repository_format):
679
return self._real_bzrdir.get_repository_transport(repository_format)
681
def get_workingtree_transport(self, workingtree_format):
683
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
685
def can_convert_format(self):
686
"""Upgrading of remote bzrdirs is not supported yet."""
689
def needs_format_conversion(self, format):
690
"""Upgrading of remote bzrdirs is not supported yet."""
693
def clone(self, url, revision_id=None, force_new_repo=False,
694
preserve_stacking=False):
696
return self._real_bzrdir.clone(url, revision_id=revision_id,
697
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
699
def _get_config(self):
700
return RemoteBzrDirConfig(self)
703
class RemoteRepositoryFormat(_mod_repository.RepositoryFormat):
704
"""Format for repositories accessed over a _SmartClient.
706
Instances of this repository are represented by RemoteRepository
709
The RemoteRepositoryFormat is parameterized during construction
710
to reflect the capabilities of the real, remote format. Specifically
711
the attributes rich_root_data and supports_tree_reference are set
712
on a per instance basis, and are not set (and should not be) at
715
:ivar _custom_format: If set, a specific concrete repository format that
716
will be used when initializing a repository with this
717
RemoteRepositoryFormat.
718
:ivar _creating_repo: If set, the repository object that this
719
RemoteRepositoryFormat was created for: it can be called into
720
to obtain data like the network name.
723
_matchingbzrdir = RemoteBzrDirFormat()
724
supports_full_versioned_files = True
725
supports_leaving_lock = True
728
_mod_repository.RepositoryFormat.__init__(self)
729
self._custom_format = None
730
self._network_name = None
731
self._creating_bzrdir = None
732
self._supports_chks = None
733
self._supports_external_lookups = None
734
self._supports_tree_reference = None
735
self._supports_funky_characters = None
736
self._rich_root_data = None
739
return "%s(_network_name=%r)" % (self.__class__.__name__,
743
def fast_deltas(self):
745
return self._custom_format.fast_deltas
748
def rich_root_data(self):
749
if self._rich_root_data is None:
751
self._rich_root_data = self._custom_format.rich_root_data
752
return self._rich_root_data
755
def supports_chks(self):
756
if self._supports_chks is None:
758
self._supports_chks = self._custom_format.supports_chks
759
return self._supports_chks
762
def supports_external_lookups(self):
763
if self._supports_external_lookups is None:
765
self._supports_external_lookups = \
766
self._custom_format.supports_external_lookups
767
return self._supports_external_lookups
770
def supports_funky_characters(self):
771
if self._supports_funky_characters is None:
773
self._supports_funky_characters = \
774
self._custom_format.supports_funky_characters
775
return self._supports_funky_characters
778
def supports_tree_reference(self):
779
if self._supports_tree_reference is None:
781
self._supports_tree_reference = \
782
self._custom_format.supports_tree_reference
783
return self._supports_tree_reference
785
def _vfs_initialize(self, a_bzrdir, shared):
786
"""Helper for common code in initialize."""
787
if self._custom_format:
788
# Custom format requested
789
result = self._custom_format.initialize(a_bzrdir, shared=shared)
790
elif self._creating_bzrdir is not None:
791
# Use the format that the repository we were created to back
793
prior_repo = self._creating_bzrdir.open_repository()
794
prior_repo._ensure_real()
795
result = prior_repo._real_repository._format.initialize(
796
a_bzrdir, shared=shared)
798
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
799
# support remote initialization.
800
# We delegate to a real object at this point (as RemoteBzrDir
801
# delegate to the repository format which would lead to infinite
802
# recursion if we just called a_bzrdir.create_repository.
803
a_bzrdir._ensure_real()
804
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
805
if not isinstance(result, RemoteRepository):
806
return self.open(a_bzrdir)
810
def initialize(self, a_bzrdir, shared=False):
811
# Being asked to create on a non RemoteBzrDir:
812
if not isinstance(a_bzrdir, RemoteBzrDir):
813
return self._vfs_initialize(a_bzrdir, shared)
814
medium = a_bzrdir._client._medium
815
if medium._is_remote_before((1, 13)):
816
return self._vfs_initialize(a_bzrdir, shared)
817
# Creating on a remote bzr dir.
818
# 1) get the network name to use.
819
if self._custom_format:
820
network_name = self._custom_format.network_name()
821
elif self._network_name:
822
network_name = self._network_name
824
# Select the current bzrlib default and ask for that.
825
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
826
reference_format = reference_bzrdir_format.repository_format
827
network_name = reference_format.network_name()
828
# 2) try direct creation via RPC
829
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
830
verb = 'BzrDir.create_repository'
836
response = a_bzrdir._call(verb, path, network_name, shared_str)
837
except errors.UnknownSmartMethod:
838
# Fallback - use vfs methods
839
medium._remember_remote_is_before((1, 13))
840
return self._vfs_initialize(a_bzrdir, shared)
842
# Turn the response into a RemoteRepository object.
843
format = response_tuple_to_repo_format(response[1:])
844
# Used to support creating a real format instance when needed.
845
format._creating_bzrdir = a_bzrdir
846
remote_repo = RemoteRepository(a_bzrdir, format)
847
format._creating_repo = remote_repo
850
def open(self, a_bzrdir):
851
if not isinstance(a_bzrdir, RemoteBzrDir):
852
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
853
return a_bzrdir.open_repository()
855
def _ensure_real(self):
856
if self._custom_format is None:
857
self._custom_format = _mod_repository.network_format_registry.get(
861
def _fetch_order(self):
863
return self._custom_format._fetch_order
866
def _fetch_uses_deltas(self):
868
return self._custom_format._fetch_uses_deltas
871
def _fetch_reconcile(self):
873
return self._custom_format._fetch_reconcile
875
def get_format_description(self):
877
return 'Remote: ' + self._custom_format.get_format_description()
879
def __eq__(self, other):
880
return self.__class__ is other.__class__
882
def network_name(self):
883
if self._network_name:
884
return self._network_name
885
self._creating_repo._ensure_real()
886
return self._creating_repo._real_repository._format.network_name()
889
def pack_compresses(self):
891
return self._custom_format.pack_compresses
894
def _serializer(self):
896
return self._custom_format._serializer
899
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
900
controldir.ControlComponent):
901
"""Repository accessed over rpc.
903
For the moment most operations are performed using local transport-backed
907
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
908
"""Create a RemoteRepository instance.
910
:param remote_bzrdir: The bzrdir hosting this repository.
911
:param format: The RemoteFormat object to use.
912
:param real_repository: If not None, a local implementation of the
913
repository logic for the repository, usually accessing the data
915
:param _client: Private testing parameter - override the smart client
916
to be used by the repository.
919
self._real_repository = real_repository
921
self._real_repository = None
922
self.bzrdir = remote_bzrdir
924
self._client = remote_bzrdir._client
926
self._client = _client
927
self._format = format
928
self._lock_mode = None
929
self._lock_token = None
931
self._leave_lock = False
932
# Cache of revision parents; misses are cached during read locks, and
933
# write locks when no _real_repository has been set.
934
self._unstacked_provider = graph.CachingParentsProvider(
935
get_parent_map=self._get_parent_map_rpc)
936
self._unstacked_provider.disable_cache()
938
# These depend on the actual remote format, so force them off for
939
# maximum compatibility. XXX: In future these should depend on the
940
# remote repository instance, but this is irrelevant until we perform
941
# reconcile via an RPC call.
942
self._reconcile_does_inventory_gc = False
943
self._reconcile_fixes_text_parents = False
944
self._reconcile_backsup_inventory = False
945
self.base = self.bzrdir.transport.base
946
# Additional places to query for data.
947
self._fallback_repositories = []
950
def user_transport(self):
951
return self.bzrdir.user_transport
954
def control_transport(self):
955
# XXX: Normally you shouldn't directly get at the remote repository
956
# transport, but I'm not sure it's worth making this method
957
# optional -- mbp 2010-04-21
958
return self.bzrdir.get_repository_transport(None)
961
return "%s(%s)" % (self.__class__.__name__, self.base)
965
def abort_write_group(self, suppress_errors=False):
966
"""Complete a write group on the decorated repository.
968
Smart methods perform operations in a single step so this API
969
is not really applicable except as a compatibility thunk
970
for older plugins that don't use e.g. the CommitBuilder
973
:param suppress_errors: see Repository.abort_write_group.
976
return self._real_repository.abort_write_group(
977
suppress_errors=suppress_errors)
981
"""Decorate the real repository for now.
983
In the long term a full blown network facility is needed to avoid
984
creating a real repository object locally.
987
return self._real_repository.chk_bytes
989
def commit_write_group(self):
990
"""Complete a write group on the decorated repository.
992
Smart methods perform operations in a single step so this API
993
is not really applicable except as a compatibility thunk
994
for older plugins that don't use e.g. the CommitBuilder
998
return self._real_repository.commit_write_group()
1000
def resume_write_group(self, tokens):
1002
return self._real_repository.resume_write_group(tokens)
1004
def suspend_write_group(self):
1006
return self._real_repository.suspend_write_group()
1008
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1010
return self._real_repository.get_missing_parent_inventories(
1011
check_for_missing_texts=check_for_missing_texts)
1013
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1015
return self._real_repository.get_rev_id_for_revno(
1018
def get_rev_id_for_revno(self, revno, known_pair):
1019
"""See Repository.get_rev_id_for_revno."""
1020
path = self.bzrdir._path_for_remote_call(self._client)
1022
if self._client._medium._is_remote_before((1, 17)):
1023
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1024
response = self._call(
1025
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1026
except errors.UnknownSmartMethod:
1027
self._client._medium._remember_remote_is_before((1, 17))
1028
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1029
if response[0] == 'ok':
1030
return True, response[1]
1031
elif response[0] == 'history-incomplete':
1032
known_pair = response[1:3]
1033
for fallback in self._fallback_repositories:
1034
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1039
# Not found in any fallbacks
1040
return False, known_pair
1042
raise errors.UnexpectedSmartServerResponse(response)
1044
def _ensure_real(self):
1045
"""Ensure that there is a _real_repository set.
1047
Used before calls to self._real_repository.
1049
Note that _ensure_real causes many roundtrips to the server which are
1050
not desirable, and prevents the use of smart one-roundtrip RPC's to
1051
perform complex operations (such as accessing parent data, streaming
1052
revisions etc). Adding calls to _ensure_real should only be done when
1053
bringing up new functionality, adding fallbacks for smart methods that
1054
require a fallback path, and never to replace an existing smart method
1055
invocation. If in doubt chat to the bzr network team.
1057
if self._real_repository is None:
1058
if 'hpssvfs' in debug.debug_flags:
1060
warning('VFS Repository access triggered\n%s',
1061
''.join(traceback.format_stack()))
1062
self._unstacked_provider.missing_keys.clear()
1063
self.bzrdir._ensure_real()
1064
self._set_real_repository(
1065
self.bzrdir._real_bzrdir.open_repository())
1067
def _translate_error(self, err, **context):
1068
self.bzrdir._translate_error(err, repository=self, **context)
1070
def find_text_key_references(self):
1071
"""Find the text key references within the repository.
1073
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1074
revision_ids. Each altered file-ids has the exact revision_ids that
1075
altered it listed explicitly.
1076
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1077
to whether they were referred to by the inventory of the
1078
revision_id that they contain. The inventory texts from all present
1079
revision ids are assessed to generate this report.
1082
return self._real_repository.find_text_key_references()
1084
def _generate_text_key_index(self):
1085
"""Generate a new text key index for the repository.
1087
This is an expensive function that will take considerable time to run.
1089
:return: A dict mapping (file_id, revision_id) tuples to a list of
1090
parents, also (file_id, revision_id) tuples.
1093
return self._real_repository._generate_text_key_index()
1095
def _get_revision_graph(self, revision_id):
1096
"""Private method for using with old (< 1.2) servers to fallback."""
1097
if revision_id is None:
1099
elif _mod_revision.is_null(revision_id):
1102
path = self.bzrdir._path_for_remote_call(self._client)
1103
response = self._call_expecting_body(
1104
'Repository.get_revision_graph', path, revision_id)
1105
response_tuple, response_handler = response
1106
if response_tuple[0] != 'ok':
1107
raise errors.UnexpectedSmartServerResponse(response_tuple)
1108
coded = response_handler.read_body_bytes()
1110
# no revisions in this repository!
1112
lines = coded.split('\n')
1115
d = tuple(line.split())
1116
revision_graph[d[0]] = d[1:]
1118
return revision_graph
1120
def _get_sink(self):
1121
"""See Repository._get_sink()."""
1122
return RemoteStreamSink(self)
1124
def _get_source(self, to_format):
1125
"""Return a source for streaming from this repository."""
1126
return RemoteStreamSource(self, to_format)
1129
def has_revision(self, revision_id):
1130
"""True if this repository has a copy of the revision."""
1131
# Copy of bzrlib.repository.Repository.has_revision
1132
return revision_id in self.has_revisions((revision_id,))
1135
def has_revisions(self, revision_ids):
1136
"""Probe to find out the presence of multiple revisions.
1138
:param revision_ids: An iterable of revision_ids.
1139
:return: A set of the revision_ids that were present.
1141
# Copy of bzrlib.repository.Repository.has_revisions
1142
parent_map = self.get_parent_map(revision_ids)
1143
result = set(parent_map)
1144
if _mod_revision.NULL_REVISION in revision_ids:
1145
result.add(_mod_revision.NULL_REVISION)
1148
def _has_same_fallbacks(self, other_repo):
1149
"""Returns true if the repositories have the same fallbacks."""
1150
# XXX: copied from Repository; it should be unified into a base class
1151
# <https://bugs.launchpad.net/bzr/+bug/401622>
1152
my_fb = self._fallback_repositories
1153
other_fb = other_repo._fallback_repositories
1154
if len(my_fb) != len(other_fb):
1156
for f, g in zip(my_fb, other_fb):
1157
if not f.has_same_location(g):
1161
def has_same_location(self, other):
1162
# TODO: Move to RepositoryBase and unify with the regular Repository
1163
# one; unfortunately the tests rely on slightly different behaviour at
1164
# present -- mbp 20090710
1165
return (self.__class__ is other.__class__ and
1166
self.bzrdir.transport.base == other.bzrdir.transport.base)
1168
def get_graph(self, other_repository=None):
1169
"""Return the graph for this repository format"""
1170
parents_provider = self._make_parents_provider(other_repository)
1171
return graph.Graph(parents_provider)
1174
def get_known_graph_ancestry(self, revision_ids):
1175
"""Return the known graph for a set of revision ids and their ancestors.
1177
st = static_tuple.StaticTuple
1178
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1179
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1180
return graph.GraphThunkIdsToKeys(known_graph)
1182
def gather_stats(self, revid=None, committers=None):
1183
"""See Repository.gather_stats()."""
1184
path = self.bzrdir._path_for_remote_call(self._client)
1185
# revid can be None to indicate no revisions, not just NULL_REVISION
1186
if revid is None or _mod_revision.is_null(revid):
1190
if committers is None or not committers:
1191
fmt_committers = 'no'
1193
fmt_committers = 'yes'
1194
response_tuple, response_handler = self._call_expecting_body(
1195
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1196
if response_tuple[0] != 'ok':
1197
raise errors.UnexpectedSmartServerResponse(response_tuple)
1199
body = response_handler.read_body_bytes()
1201
for line in body.split('\n'):
1204
key, val_text = line.split(':')
1205
if key in ('revisions', 'size', 'committers'):
1206
result[key] = int(val_text)
1207
elif key in ('firstrev', 'latestrev'):
1208
values = val_text.split(' ')[1:]
1209
result[key] = (float(values[0]), long(values[1]))
1213
def find_branches(self, using=False):
1214
"""See Repository.find_branches()."""
1215
# should be an API call to the server.
1217
return self._real_repository.find_branches(using=using)
1219
def get_physical_lock_status(self):
1220
"""See Repository.get_physical_lock_status()."""
1221
# should be an API call to the server.
1223
return self._real_repository.get_physical_lock_status()
1225
def is_in_write_group(self):
1226
"""Return True if there is an open write group.
1228
write groups are only applicable locally for the smart server..
1230
if self._real_repository:
1231
return self._real_repository.is_in_write_group()
1233
def is_locked(self):
1234
return self._lock_count >= 1
1236
def is_shared(self):
1237
"""See Repository.is_shared()."""
1238
path = self.bzrdir._path_for_remote_call(self._client)
1239
response = self._call('Repository.is_shared', path)
1240
if response[0] not in ('yes', 'no'):
1241
raise SmartProtocolError('unexpected response code %s' % (response,))
1242
return response[0] == 'yes'
1244
def is_write_locked(self):
1245
return self._lock_mode == 'w'
1247
def _warn_if_deprecated(self, branch=None):
1248
# If we have a real repository, the check will be done there, if we
1249
# don't the check will be done remotely.
1252
def lock_read(self):
1253
"""Lock the repository for read operations.
1255
:return: A bzrlib.lock.LogicalLockResult.
1257
# wrong eventually - want a local lock cache context
1258
if not self._lock_mode:
1259
self._note_lock('r')
1260
self._lock_mode = 'r'
1261
self._lock_count = 1
1262
self._unstacked_provider.enable_cache(cache_misses=True)
1263
if self._real_repository is not None:
1264
self._real_repository.lock_read()
1265
for repo in self._fallback_repositories:
1268
self._lock_count += 1
1269
return lock.LogicalLockResult(self.unlock)
1271
def _remote_lock_write(self, token):
1272
path = self.bzrdir._path_for_remote_call(self._client)
1275
err_context = {'token': token}
1276
response = self._call('Repository.lock_write', path, token,
1278
if response[0] == 'ok':
1279
ok, token = response
1282
raise errors.UnexpectedSmartServerResponse(response)
1284
def lock_write(self, token=None, _skip_rpc=False):
1285
if not self._lock_mode:
1286
self._note_lock('w')
1288
if self._lock_token is not None:
1289
if token != self._lock_token:
1290
raise errors.TokenMismatch(token, self._lock_token)
1291
self._lock_token = token
1293
self._lock_token = self._remote_lock_write(token)
1294
# if self._lock_token is None, then this is something like packs or
1295
# svn where we don't get to lock the repo, or a weave style repository
1296
# where we cannot lock it over the wire and attempts to do so will
1298
if self._real_repository is not None:
1299
self._real_repository.lock_write(token=self._lock_token)
1300
if token is not None:
1301
self._leave_lock = True
1303
self._leave_lock = False
1304
self._lock_mode = 'w'
1305
self._lock_count = 1
1306
cache_misses = self._real_repository is None
1307
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1308
for repo in self._fallback_repositories:
1309
# Writes don't affect fallback repos
1311
elif self._lock_mode == 'r':
1312
raise errors.ReadOnlyError(self)
1314
self._lock_count += 1
1315
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1317
def leave_lock_in_place(self):
1318
if not self._lock_token:
1319
raise NotImplementedError(self.leave_lock_in_place)
1320
self._leave_lock = True
1322
def dont_leave_lock_in_place(self):
1323
if not self._lock_token:
1324
raise NotImplementedError(self.dont_leave_lock_in_place)
1325
self._leave_lock = False
1327
def _set_real_repository(self, repository):
1328
"""Set the _real_repository for this repository.
1330
:param repository: The repository to fallback to for non-hpss
1331
implemented operations.
1333
if self._real_repository is not None:
1334
# Replacing an already set real repository.
1335
# We cannot do this [currently] if the repository is locked -
1336
# synchronised state might be lost.
1337
if self.is_locked():
1338
raise AssertionError('_real_repository is already set')
1339
if isinstance(repository, RemoteRepository):
1340
raise AssertionError()
1341
self._real_repository = repository
1342
# three code paths happen here:
1343
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1344
# up stacking. In this case self._fallback_repositories is [], and the
1345
# real repo is already setup. Preserve the real repo and
1346
# RemoteRepository.add_fallback_repository will avoid adding
1348
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1349
# ensure_real is triggered from a branch, the real repository to
1350
# set already has a matching list with separate instances, but
1351
# as they are also RemoteRepositories we don't worry about making the
1352
# lists be identical.
1353
# 3) new servers, RemoteRepository.ensure_real is triggered before
1354
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1355
# and need to populate it.
1356
if (self._fallback_repositories and
1357
len(self._real_repository._fallback_repositories) !=
1358
len(self._fallback_repositories)):
1359
if len(self._real_repository._fallback_repositories):
1360
raise AssertionError(
1361
"cannot cleanly remove existing _fallback_repositories")
1362
for fb in self._fallback_repositories:
1363
self._real_repository.add_fallback_repository(fb)
1364
if self._lock_mode == 'w':
1365
# if we are already locked, the real repository must be able to
1366
# acquire the lock with our token.
1367
self._real_repository.lock_write(self._lock_token)
1368
elif self._lock_mode == 'r':
1369
self._real_repository.lock_read()
1371
def start_write_group(self):
1372
"""Start a write group on the decorated repository.
1374
Smart methods perform operations in a single step so this API
1375
is not really applicable except as a compatibility thunk
1376
for older plugins that don't use e.g. the CommitBuilder
1380
return self._real_repository.start_write_group()
1382
def _unlock(self, token):
1383
path = self.bzrdir._path_for_remote_call(self._client)
1385
# with no token the remote repository is not persistently locked.
1387
err_context = {'token': token}
1388
response = self._call('Repository.unlock', path, token,
1390
if response == ('ok',):
1393
raise errors.UnexpectedSmartServerResponse(response)
1395
@only_raises(errors.LockNotHeld, errors.LockBroken)
1397
if not self._lock_count:
1398
return lock.cant_unlock_not_held(self)
1399
self._lock_count -= 1
1400
if self._lock_count > 0:
1402
self._unstacked_provider.disable_cache()
1403
old_mode = self._lock_mode
1404
self._lock_mode = None
1406
# The real repository is responsible at present for raising an
1407
# exception if it's in an unfinished write group. However, it
1408
# normally will *not* actually remove the lock from disk - that's
1409
# done by the server on receiving the Repository.unlock call.
1410
# This is just to let the _real_repository stay up to date.
1411
if self._real_repository is not None:
1412
self._real_repository.unlock()
1414
# The rpc-level lock should be released even if there was a
1415
# problem releasing the vfs-based lock.
1417
# Only write-locked repositories need to make a remote method
1418
# call to perform the unlock.
1419
old_token = self._lock_token
1420
self._lock_token = None
1421
if not self._leave_lock:
1422
self._unlock(old_token)
1423
# Fallbacks are always 'lock_read()' so we don't pay attention to
1425
for repo in self._fallback_repositories:
1428
def break_lock(self):
1429
# should hand off to the network
1431
return self._real_repository.break_lock()
1433
def _get_tarball(self, compression):
1434
"""Return a TemporaryFile containing a repository tarball.
1436
Returns None if the server does not support sending tarballs.
1439
path = self.bzrdir._path_for_remote_call(self._client)
1441
response, protocol = self._call_expecting_body(
1442
'Repository.tarball', path, compression)
1443
except errors.UnknownSmartMethod:
1444
protocol.cancel_read_body()
1446
if response[0] == 'ok':
1447
# Extract the tarball and return it
1448
t = tempfile.NamedTemporaryFile()
1449
# TODO: rpc layer should read directly into it...
1450
t.write(protocol.read_body_bytes())
1453
raise errors.UnexpectedSmartServerResponse(response)
1455
def sprout(self, to_bzrdir, revision_id=None):
1456
# TODO: Option to control what format is created?
1458
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1460
dest_repo.fetch(self, revision_id=revision_id)
1463
### These methods are just thin shims to the VFS object for now.
1465
def revision_tree(self, revision_id):
1467
return self._real_repository.revision_tree(revision_id)
1469
def get_serializer_format(self):
1471
return self._real_repository.get_serializer_format()
1473
def get_commit_builder(self, branch, parents, config, timestamp=None,
1474
timezone=None, committer=None, revprops=None,
1476
# FIXME: It ought to be possible to call this without immediately
1477
# triggering _ensure_real. For now it's the easiest thing to do.
1479
real_repo = self._real_repository
1480
builder = real_repo.get_commit_builder(branch, parents,
1481
config, timestamp=timestamp, timezone=timezone,
1482
committer=committer, revprops=revprops, revision_id=revision_id)
1485
def add_fallback_repository(self, repository):
1486
"""Add a repository to use for looking up data not held locally.
1488
:param repository: A repository.
1490
if not self._format.supports_external_lookups:
1491
raise errors.UnstackableRepositoryFormat(
1492
self._format.network_name(), self.base)
1493
# We need to accumulate additional repositories here, to pass them in
1496
if self.is_locked():
1497
# We will call fallback.unlock() when we transition to the unlocked
1498
# state, so always add a lock here. If a caller passes us a locked
1499
# repository, they are responsible for unlocking it later.
1500
repository.lock_read()
1501
self._check_fallback_repository(repository)
1502
self._fallback_repositories.append(repository)
1503
# If self._real_repository was parameterised already (e.g. because a
1504
# _real_branch had its get_stacked_on_url method called), then the
1505
# repository to be added may already be in the _real_repositories list.
1506
if self._real_repository is not None:
1507
fallback_locations = [repo.user_url for repo in
1508
self._real_repository._fallback_repositories]
1509
if repository.user_url not in fallback_locations:
1510
self._real_repository.add_fallback_repository(repository)
1512
def _check_fallback_repository(self, repository):
1513
"""Check that this repository can fallback to repository safely.
1515
Raise an error if not.
1517
:param repository: A repository to fallback to.
1519
return _mod_repository.InterRepository._assert_same_model(
1522
def add_inventory(self, revid, inv, parents):
1524
return self._real_repository.add_inventory(revid, inv, parents)
1526
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1527
parents, basis_inv=None, propagate_caches=False):
1529
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1530
delta, new_revision_id, parents, basis_inv=basis_inv,
1531
propagate_caches=propagate_caches)
1533
def add_revision(self, rev_id, rev, inv=None, config=None):
1535
return self._real_repository.add_revision(
1536
rev_id, rev, inv=inv, config=config)
1539
def get_inventory(self, revision_id):
1541
return self._real_repository.get_inventory(revision_id)
1543
def iter_inventories(self, revision_ids, ordering=None):
1545
return self._real_repository.iter_inventories(revision_ids, ordering)
1548
def get_revision(self, revision_id):
1550
return self._real_repository.get_revision(revision_id)
1552
def get_transaction(self):
1554
return self._real_repository.get_transaction()
1557
def clone(self, a_bzrdir, revision_id=None):
1559
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1561
def make_working_trees(self):
1562
"""See Repository.make_working_trees"""
1564
return self._real_repository.make_working_trees()
1566
def refresh_data(self):
1567
"""Re-read any data needed to synchronise with disk.
1569
This method is intended to be called after another repository instance
1570
(such as one used by a smart server) has inserted data into the
1571
repository. On all repositories this will work outside of write groups.
1572
Some repository formats (pack and newer for bzrlib native formats)
1573
support refresh_data inside write groups. If called inside a write
1574
group on a repository that does not support refreshing in a write group
1575
IsInWriteGroupError will be raised.
1577
if self._real_repository is not None:
1578
self._real_repository.refresh_data()
1580
def revision_ids_to_search_result(self, result_set):
1581
"""Convert a set of revision ids to a graph SearchResult."""
1582
result_parents = set()
1583
for parents in self.get_graph().get_parent_map(
1584
result_set).itervalues():
1585
result_parents.update(parents)
1586
included_keys = result_set.intersection(result_parents)
1587
start_keys = result_set.difference(included_keys)
1588
exclude_keys = result_parents.difference(result_set)
1589
result = graph.SearchResult(start_keys, exclude_keys,
1590
len(result_set), result_set)
1594
def search_missing_revision_ids(self, other,
1595
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1596
find_ghosts=True, revision_ids=None, if_present_ids=None):
1597
"""Return the revision ids that other has that this does not.
1599
These are returned in topological order.
1601
revision_id: only return revision ids included by revision_id.
1603
if symbol_versioning.deprecated_passed(revision_id):
1604
symbol_versioning.warn(
1605
'search_missing_revision_ids(revision_id=...) was '
1606
'deprecated in 2.4. Use revision_ids=[...] instead.',
1607
DeprecationWarning, stacklevel=2)
1608
if revision_ids is not None:
1609
raise AssertionError(
1610
'revision_ids is mutually exclusive with revision_id')
1611
if revision_id is not None:
1612
revision_ids = [revision_id]
1613
inter_repo = _mod_repository.InterRepository.get(other, self)
1614
return inter_repo.search_missing_revision_ids(
1615
find_ghosts=find_ghosts, revision_ids=revision_ids,
1616
if_present_ids=if_present_ids)
1618
def fetch(self, source, revision_id=None, find_ghosts=False,
1620
# No base implementation to use as RemoteRepository is not a subclass
1621
# of Repository; so this is a copy of Repository.fetch().
1622
if fetch_spec is not None and revision_id is not None:
1623
raise AssertionError(
1624
"fetch_spec and revision_id are mutually exclusive.")
1625
if self.is_in_write_group():
1626
raise errors.InternalBzrError(
1627
"May not fetch while in a write group.")
1628
# fast path same-url fetch operations
1629
if (self.has_same_location(source)
1630
and fetch_spec is None
1631
and self._has_same_fallbacks(source)):
1632
# check that last_revision is in 'from' and then return a
1634
if (revision_id is not None and
1635
not _mod_revision.is_null(revision_id)):
1636
self.get_revision(revision_id)
1638
# if there is no specific appropriate InterRepository, this will get
1639
# the InterRepository base class, which raises an
1640
# IncompatibleRepositories when asked to fetch.
1641
inter = _mod_repository.InterRepository.get(source, self)
1642
return inter.fetch(revision_id=revision_id,
1643
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1645
def create_bundle(self, target, base, fileobj, format=None):
1647
self._real_repository.create_bundle(target, base, fileobj, format)
1650
def get_ancestry(self, revision_id, topo_sorted=True):
1652
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1654
def fileids_altered_by_revision_ids(self, revision_ids):
1656
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1658
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1660
return self._real_repository._get_versioned_file_checker(
1661
revisions, revision_versions_cache)
1663
def iter_files_bytes(self, desired_files):
1664
"""See Repository.iter_file_bytes.
1667
return self._real_repository.iter_files_bytes(desired_files)
1669
def get_parent_map(self, revision_ids):
1670
"""See bzrlib.Graph.get_parent_map()."""
1671
return self._make_parents_provider().get_parent_map(revision_ids)
1673
def _get_parent_map_rpc(self, keys):
1674
"""Helper for get_parent_map that performs the RPC."""
1675
medium = self._client._medium
1676
if medium._is_remote_before((1, 2)):
1677
# We already found out that the server can't understand
1678
# Repository.get_parent_map requests, so just fetch the whole
1681
# Note that this reads the whole graph, when only some keys are
1682
# wanted. On this old server there's no way (?) to get them all
1683
# in one go, and the user probably will have seen a warning about
1684
# the server being old anyhow.
1685
rg = self._get_revision_graph(None)
1686
# There is an API discrepancy between get_parent_map and
1687
# get_revision_graph. Specifically, a "key:()" pair in
1688
# get_revision_graph just means a node has no parents. For
1689
# "get_parent_map" it means the node is a ghost. So fix up the
1690
# graph to correct this.
1691
# https://bugs.launchpad.net/bzr/+bug/214894
1692
# There is one other "bug" which is that ghosts in
1693
# get_revision_graph() are not returned at all. But we won't worry
1694
# about that for now.
1695
for node_id, parent_ids in rg.iteritems():
1696
if parent_ids == ():
1697
rg[node_id] = (NULL_REVISION,)
1698
rg[NULL_REVISION] = ()
1703
raise ValueError('get_parent_map(None) is not valid')
1704
if NULL_REVISION in keys:
1705
keys.discard(NULL_REVISION)
1706
found_parents = {NULL_REVISION:()}
1708
return found_parents
1711
# TODO(Needs analysis): We could assume that the keys being requested
1712
# from get_parent_map are in a breadth first search, so typically they
1713
# will all be depth N from some common parent, and we don't have to
1714
# have the server iterate from the root parent, but rather from the
1715
# keys we're searching; and just tell the server the keyspace we
1716
# already have; but this may be more traffic again.
1718
# Transform self._parents_map into a search request recipe.
1719
# TODO: Manage this incrementally to avoid covering the same path
1720
# repeatedly. (The server will have to on each request, but the less
1721
# work done the better).
1723
# Negative caching notes:
1724
# new server sends missing when a request including the revid
1725
# 'include-missing:' is present in the request.
1726
# missing keys are serialised as missing:X, and we then call
1727
# provider.note_missing(X) for-all X
1728
parents_map = self._unstacked_provider.get_cached_map()
1729
if parents_map is None:
1730
# Repository is not locked, so there's no cache.
1732
# start_set is all the keys in the cache
1733
start_set = set(parents_map)
1734
# result set is all the references to keys in the cache
1735
result_parents = set()
1736
for parents in parents_map.itervalues():
1737
result_parents.update(parents)
1738
stop_keys = result_parents.difference(start_set)
1739
# We don't need to send ghosts back to the server as a position to
1741
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1742
key_count = len(parents_map)
1743
if (NULL_REVISION in result_parents
1744
and NULL_REVISION in self._unstacked_provider.missing_keys):
1745
# If we pruned NULL_REVISION from the stop_keys because it's also
1746
# in our cache of "missing" keys we need to increment our key count
1747
# by 1, because the reconsitituted SearchResult on the server will
1748
# still consider NULL_REVISION to be an included key.
1750
included_keys = start_set.intersection(result_parents)
1751
start_set.difference_update(included_keys)
1752
recipe = ('manual', start_set, stop_keys, key_count)
1753
body = self._serialise_search_recipe(recipe)
1754
path = self.bzrdir._path_for_remote_call(self._client)
1756
if type(key) is not str:
1758
"key %r not a plain string" % (key,))
1759
verb = 'Repository.get_parent_map'
1760
args = (path, 'include-missing:') + tuple(keys)
1762
response = self._call_with_body_bytes_expecting_body(
1764
except errors.UnknownSmartMethod:
1765
# Server does not support this method, so get the whole graph.
1766
# Worse, we have to force a disconnection, because the server now
1767
# doesn't realise it has a body on the wire to consume, so the
1768
# only way to recover is to abandon the connection.
1770
'Server is too old for fast get_parent_map, reconnecting. '
1771
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1773
# To avoid having to disconnect repeatedly, we keep track of the
1774
# fact the server doesn't understand remote methods added in 1.2.
1775
medium._remember_remote_is_before((1, 2))
1776
# Recurse just once and we should use the fallback code.
1777
return self._get_parent_map_rpc(keys)
1778
response_tuple, response_handler = response
1779
if response_tuple[0] not in ['ok']:
1780
response_handler.cancel_read_body()
1781
raise errors.UnexpectedSmartServerResponse(response_tuple)
1782
if response_tuple[0] == 'ok':
1783
coded = bz2.decompress(response_handler.read_body_bytes())
1785
# no revisions found
1787
lines = coded.split('\n')
1790
d = tuple(line.split())
1792
revision_graph[d[0]] = d[1:]
1795
if d[0].startswith('missing:'):
1797
self._unstacked_provider.note_missing_key(revid)
1799
# no parents - so give the Graph result
1801
revision_graph[d[0]] = (NULL_REVISION,)
1802
return revision_graph
1805
def get_signature_text(self, revision_id):
1807
return self._real_repository.get_signature_text(revision_id)
1810
def _get_inventory_xml(self, revision_id):
1812
return self._real_repository._get_inventory_xml(revision_id)
1814
def reconcile(self, other=None, thorough=False):
1816
return self._real_repository.reconcile(other=other, thorough=thorough)
1818
def all_revision_ids(self):
1820
return self._real_repository.all_revision_ids()
1823
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1825
return self._real_repository.get_deltas_for_revisions(revisions,
1826
specific_fileids=specific_fileids)
1829
def get_revision_delta(self, revision_id, specific_fileids=None):
1831
return self._real_repository.get_revision_delta(revision_id,
1832
specific_fileids=specific_fileids)
1835
def revision_trees(self, revision_ids):
1837
return self._real_repository.revision_trees(revision_ids)
1840
def get_revision_reconcile(self, revision_id):
1842
return self._real_repository.get_revision_reconcile(revision_id)
1845
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1847
return self._real_repository.check(revision_ids=revision_ids,
1848
callback_refs=callback_refs, check_repo=check_repo)
1850
def copy_content_into(self, destination, revision_id=None):
1852
return self._real_repository.copy_content_into(
1853
destination, revision_id=revision_id)
1855
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1856
# get a tarball of the remote repository, and copy from that into the
1858
from bzrlib import osutils
1860
# TODO: Maybe a progress bar while streaming the tarball?
1861
note("Copying repository content as tarball...")
1862
tar_file = self._get_tarball('bz2')
1863
if tar_file is None:
1865
destination = to_bzrdir.create_repository()
1867
tar = tarfile.open('repository', fileobj=tar_file,
1869
tmpdir = osutils.mkdtemp()
1871
_extract_tar(tar, tmpdir)
1872
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1873
tmp_repo = tmp_bzrdir.open_repository()
1874
tmp_repo.copy_content_into(destination, revision_id)
1876
osutils.rmtree(tmpdir)
1880
# TODO: Suggestion from john: using external tar is much faster than
1881
# python's tarfile library, but it may not work on windows.
1884
def inventories(self):
1885
"""Decorate the real repository for now.
1887
In the long term a full blown network facility is needed to
1888
avoid creating a real repository object locally.
1891
return self._real_repository.inventories
1894
def pack(self, hint=None, clean_obsolete_packs=False):
1895
"""Compress the data within the repository.
1897
This is not currently implemented within the smart server.
1900
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1903
def revisions(self):
1904
"""Decorate the real repository for now.
1906
In the short term this should become a real object to intercept graph
1909
In the long term a full blown network facility is needed.
1912
return self._real_repository.revisions
1914
def set_make_working_trees(self, new_value):
1916
new_value_str = "True"
1918
new_value_str = "False"
1919
path = self.bzrdir._path_for_remote_call(self._client)
1921
response = self._call(
1922
'Repository.set_make_working_trees', path, new_value_str)
1923
except errors.UnknownSmartMethod:
1925
self._real_repository.set_make_working_trees(new_value)
1927
if response[0] != 'ok':
1928
raise errors.UnexpectedSmartServerResponse(response)
1931
def signatures(self):
1932
"""Decorate the real repository for now.
1934
In the long term a full blown network facility is needed to avoid
1935
creating a real repository object locally.
1938
return self._real_repository.signatures
1941
def sign_revision(self, revision_id, gpg_strategy):
1943
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1947
"""Decorate the real repository for now.
1949
In the long term a full blown network facility is needed to avoid
1950
creating a real repository object locally.
1953
return self._real_repository.texts
1956
def get_revisions(self, revision_ids):
1958
return self._real_repository.get_revisions(revision_ids)
1960
def supports_rich_root(self):
1961
return self._format.rich_root_data
1963
def iter_reverse_revision_history(self, revision_id):
1965
return self._real_repository.iter_reverse_revision_history(revision_id)
1968
def _serializer(self):
1969
return self._format._serializer
1971
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1973
return self._real_repository.store_revision_signature(
1974
gpg_strategy, plaintext, revision_id)
1976
def add_signature_text(self, revision_id, signature):
1978
return self._real_repository.add_signature_text(revision_id, signature)
1980
def has_signature_for_revision_id(self, revision_id):
1982
return self._real_repository.has_signature_for_revision_id(revision_id)
1984
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1986
return self._real_repository.item_keys_introduced_by(revision_ids,
1987
_files_pb=_files_pb)
1989
def revision_graph_can_have_wrong_parents(self):
1990
# The answer depends on the remote repo format.
1992
return self._real_repository.revision_graph_can_have_wrong_parents()
1994
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1996
return self._real_repository._find_inconsistent_revision_parents(
1999
def _check_for_inconsistent_revision_parents(self):
2001
return self._real_repository._check_for_inconsistent_revision_parents()
2003
def _make_parents_provider(self, other=None):
2004
providers = [self._unstacked_provider]
2005
if other is not None:
2006
providers.insert(0, other)
2007
providers.extend(r._make_parents_provider() for r in
2008
self._fallback_repositories)
2009
return graph.StackedParentsProvider(providers)
2011
def _serialise_search_recipe(self, recipe):
2012
"""Serialise a graph search recipe.
2014
:param recipe: A search recipe (start, stop, count).
2015
:return: Serialised bytes.
2017
start_keys = ' '.join(recipe[1])
2018
stop_keys = ' '.join(recipe[2])
2019
count = str(recipe[3])
2020
return '\n'.join((start_keys, stop_keys, count))
2022
def _serialise_search_result(self, search_result):
2023
parts = search_result.get_network_struct()
2024
return '\n'.join(parts)
2027
path = self.bzrdir._path_for_remote_call(self._client)
2029
response = self._call('PackRepository.autopack', path)
2030
except errors.UnknownSmartMethod:
2032
self._real_repository._pack_collection.autopack()
2035
if response[0] != 'ok':
2036
raise errors.UnexpectedSmartServerResponse(response)
2039
class RemoteStreamSink(_mod_repository.StreamSink):
2041
def _insert_real(self, stream, src_format, resume_tokens):
2042
self.target_repo._ensure_real()
2043
sink = self.target_repo._real_repository._get_sink()
2044
result = sink.insert_stream(stream, src_format, resume_tokens)
2046
self.target_repo.autopack()
2049
def insert_stream(self, stream, src_format, resume_tokens):
2050
target = self.target_repo
2051
target._unstacked_provider.missing_keys.clear()
2052
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2053
if target._lock_token:
2054
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2055
lock_args = (target._lock_token or '',)
2057
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2059
client = target._client
2060
medium = client._medium
2061
path = target.bzrdir._path_for_remote_call(client)
2062
# Probe for the verb to use with an empty stream before sending the
2063
# real stream to it. We do this both to avoid the risk of sending a
2064
# large request that is then rejected, and because we don't want to
2065
# implement a way to buffer, rewind, or restart the stream.
2067
for verb, required_version in candidate_calls:
2068
if medium._is_remote_before(required_version):
2071
# We've already done the probing (and set _is_remote_before) on
2072
# a previous insert.
2075
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2077
response = client.call_with_body_stream(
2078
(verb, path, '') + lock_args, byte_stream)
2079
except errors.UnknownSmartMethod:
2080
medium._remember_remote_is_before(required_version)
2086
return self._insert_real(stream, src_format, resume_tokens)
2087
self._last_inv_record = None
2088
self._last_substream = None
2089
if required_version < (1, 19):
2090
# Remote side doesn't support inventory deltas. Wrap the stream to
2091
# make sure we don't send any. If the stream contains inventory
2092
# deltas we'll interrupt the smart insert_stream request and
2094
stream = self._stop_stream_if_inventory_delta(stream)
2095
byte_stream = smart_repo._stream_to_byte_stream(
2097
resume_tokens = ' '.join(resume_tokens)
2098
response = client.call_with_body_stream(
2099
(verb, path, resume_tokens) + lock_args, byte_stream)
2100
if response[0][0] not in ('ok', 'missing-basis'):
2101
raise errors.UnexpectedSmartServerResponse(response)
2102
if self._last_substream is not None:
2103
# The stream included an inventory-delta record, but the remote
2104
# side isn't new enough to support them. So we need to send the
2105
# rest of the stream via VFS.
2106
self.target_repo.refresh_data()
2107
return self._resume_stream_with_vfs(response, src_format)
2108
if response[0][0] == 'missing-basis':
2109
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2110
resume_tokens = tokens
2111
return resume_tokens, set(missing_keys)
2113
self.target_repo.refresh_data()
2116
def _resume_stream_with_vfs(self, response, src_format):
2117
"""Resume sending a stream via VFS, first resending the record and
2118
substream that couldn't be sent via an insert_stream verb.
2120
if response[0][0] == 'missing-basis':
2121
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2122
# Ignore missing_keys, we haven't finished inserting yet
2125
def resume_substream():
2126
# Yield the substream that was interrupted.
2127
for record in self._last_substream:
2129
self._last_substream = None
2130
def resume_stream():
2131
# Finish sending the interrupted substream
2132
yield ('inventory-deltas', resume_substream())
2133
# Then simply continue sending the rest of the stream.
2134
for substream_kind, substream in self._last_stream:
2135
yield substream_kind, substream
2136
return self._insert_real(resume_stream(), src_format, tokens)
2138
def _stop_stream_if_inventory_delta(self, stream):
2139
"""Normally this just lets the original stream pass-through unchanged.
2141
However if any 'inventory-deltas' substream occurs it will stop
2142
streaming, and store the interrupted substream and stream in
2143
self._last_substream and self._last_stream so that the stream can be
2144
resumed by _resume_stream_with_vfs.
2147
stream_iter = iter(stream)
2148
for substream_kind, substream in stream_iter:
2149
if substream_kind == 'inventory-deltas':
2150
self._last_substream = substream
2151
self._last_stream = stream_iter
2154
yield substream_kind, substream
2157
class RemoteStreamSource(_mod_repository.StreamSource):
2158
"""Stream data from a remote server."""
2160
def get_stream(self, search):
2161
if (self.from_repository._fallback_repositories and
2162
self.to_format._fetch_order == 'topological'):
2163
return self._real_stream(self.from_repository, search)
2166
repos = [self.from_repository]
2172
repos.extend(repo._fallback_repositories)
2173
sources.append(repo)
2174
return self.missing_parents_chain(search, sources)
2176
def get_stream_for_missing_keys(self, missing_keys):
2177
self.from_repository._ensure_real()
2178
real_repo = self.from_repository._real_repository
2179
real_source = real_repo._get_source(self.to_format)
2180
return real_source.get_stream_for_missing_keys(missing_keys)
2182
def _real_stream(self, repo, search):
2183
"""Get a stream for search from repo.
2185
This never called RemoteStreamSource.get_stream, and is a heler
2186
for RemoteStreamSource._get_stream to allow getting a stream
2187
reliably whether fallback back because of old servers or trying
2188
to stream from a non-RemoteRepository (which the stacked support
2191
source = repo._get_source(self.to_format)
2192
if isinstance(source, RemoteStreamSource):
2194
source = repo._real_repository._get_source(self.to_format)
2195
return source.get_stream(search)
2197
def _get_stream(self, repo, search):
2198
"""Core worker to get a stream from repo for search.
2200
This is used by both get_stream and the stacking support logic. It
2201
deliberately gets a stream for repo which does not need to be
2202
self.from_repository. In the event that repo is not Remote, or
2203
cannot do a smart stream, a fallback is made to the generic
2204
repository._get_stream() interface, via self._real_stream.
2206
In the event of stacking, streams from _get_stream will not
2207
contain all the data for search - this is normal (see get_stream).
2209
:param repo: A repository.
2210
:param search: A search.
2212
# Fallbacks may be non-smart
2213
if not isinstance(repo, RemoteRepository):
2214
return self._real_stream(repo, search)
2215
client = repo._client
2216
medium = client._medium
2217
path = repo.bzrdir._path_for_remote_call(client)
2218
search_bytes = repo._serialise_search_result(search)
2219
args = (path, self.to_format.network_name())
2221
('Repository.get_stream_1.19', (1, 19)),
2222
('Repository.get_stream', (1, 13))]
2225
for verb, version in candidate_verbs:
2226
if medium._is_remote_before(version):
2229
response = repo._call_with_body_bytes_expecting_body(
2230
verb, args, search_bytes)
2231
except errors.UnknownSmartMethod:
2232
medium._remember_remote_is_before(version)
2233
except errors.UnknownErrorFromSmartServer, e:
2234
if isinstance(search, graph.EverythingResult):
2235
error_verb = e.error_from_smart_server.error_verb
2236
if error_verb == 'BadSearch':
2237
# Pre-2.4 servers don't support this sort of search.
2238
# XXX: perhaps falling back to VFS on BadSearch is a
2239
# good idea in general? It might provide a little bit
2240
# of protection against client-side bugs.
2241
medium._remember_remote_is_before((2, 4))
2245
response_tuple, response_handler = response
2249
return self._real_stream(repo, search)
2250
if response_tuple[0] != 'ok':
2251
raise errors.UnexpectedSmartServerResponse(response_tuple)
2252
byte_stream = response_handler.read_streamed_body()
2253
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2254
self._record_counter)
2255
if src_format.network_name() != repo._format.network_name():
2256
raise AssertionError(
2257
"Mismatched RemoteRepository and stream src %r, %r" % (
2258
src_format.network_name(), repo._format.network_name()))
2261
def missing_parents_chain(self, search, sources):
2262
"""Chain multiple streams together to handle stacking.
2264
:param search: The overall search to satisfy with streams.
2265
:param sources: A list of Repository objects to query.
2267
self.from_serialiser = self.from_repository._format._serializer
2268
self.seen_revs = set()
2269
self.referenced_revs = set()
2270
# If there are heads in the search, or the key count is > 0, we are not
2272
while not search.is_empty() and len(sources) > 1:
2273
source = sources.pop(0)
2274
stream = self._get_stream(source, search)
2275
for kind, substream in stream:
2276
if kind != 'revisions':
2277
yield kind, substream
2279
yield kind, self.missing_parents_rev_handler(substream)
2280
search = search.refine(self.seen_revs, self.referenced_revs)
2281
self.seen_revs = set()
2282
self.referenced_revs = set()
2283
if not search.is_empty():
2284
for kind, stream in self._get_stream(sources[0], search):
2287
def missing_parents_rev_handler(self, substream):
2288
for content in substream:
2289
revision_bytes = content.get_bytes_as('fulltext')
2290
revision = self.from_serialiser.read_revision_from_string(
2292
self.seen_revs.add(content.key[-1])
2293
self.referenced_revs.update(revision.parent_ids)
2297
class RemoteBranchLockableFiles(LockableFiles):
2298
"""A 'LockableFiles' implementation that talks to a smart server.
2300
This is not a public interface class.
2303
def __init__(self, bzrdir, _client):
2304
self.bzrdir = bzrdir
2305
self._client = _client
2306
self._need_find_modes = True
2307
LockableFiles.__init__(
2308
self, bzrdir.get_branch_transport(None),
2309
'lock', lockdir.LockDir)
2311
def _find_modes(self):
2312
# RemoteBranches don't let the client set the mode of control files.
2313
self._dir_mode = None
2314
self._file_mode = None
2317
class RemoteBranchFormat(branch.BranchFormat):
2319
def __init__(self, network_name=None):
2320
super(RemoteBranchFormat, self).__init__()
2321
self._matchingbzrdir = RemoteBzrDirFormat()
2322
self._matchingbzrdir.set_branch_format(self)
2323
self._custom_format = None
2324
self._network_name = network_name
2326
def __eq__(self, other):
2327
return (isinstance(other, RemoteBranchFormat) and
2328
self.__dict__ == other.__dict__)
2330
def _ensure_real(self):
2331
if self._custom_format is None:
2332
self._custom_format = branch.network_format_registry.get(
2335
def get_format_description(self):
2337
return 'Remote: ' + self._custom_format.get_format_description()
2339
def network_name(self):
2340
return self._network_name
2342
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2343
return a_bzrdir.open_branch(name=name,
2344
ignore_fallbacks=ignore_fallbacks)
2346
def _vfs_initialize(self, a_bzrdir, name):
2347
# Initialisation when using a local bzrdir object, or a non-vfs init
2348
# method is not available on the server.
2349
# self._custom_format is always set - the start of initialize ensures
2351
if isinstance(a_bzrdir, RemoteBzrDir):
2352
a_bzrdir._ensure_real()
2353
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2356
# We assume the bzrdir is parameterised; it may not be.
2357
result = self._custom_format.initialize(a_bzrdir, name)
2358
if (isinstance(a_bzrdir, RemoteBzrDir) and
2359
not isinstance(result, RemoteBranch)):
2360
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2364
def initialize(self, a_bzrdir, name=None, repository=None):
2365
# 1) get the network name to use.
2366
if self._custom_format:
2367
network_name = self._custom_format.network_name()
2369
# Select the current bzrlib default and ask for that.
2370
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2371
reference_format = reference_bzrdir_format.get_branch_format()
2372
self._custom_format = reference_format
2373
network_name = reference_format.network_name()
2374
# Being asked to create on a non RemoteBzrDir:
2375
if not isinstance(a_bzrdir, RemoteBzrDir):
2376
return self._vfs_initialize(a_bzrdir, name=name)
2377
medium = a_bzrdir._client._medium
2378
if medium._is_remote_before((1, 13)):
2379
return self._vfs_initialize(a_bzrdir, name=name)
2380
# Creating on a remote bzr dir.
2381
# 2) try direct creation via RPC
2382
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2383
if name is not None:
2384
# XXX JRV20100304: Support creating colocated branches
2385
raise errors.NoColocatedBranchSupport(self)
2386
verb = 'BzrDir.create_branch'
2388
response = a_bzrdir._call(verb, path, network_name)
2389
except errors.UnknownSmartMethod:
2390
# Fallback - use vfs methods
2391
medium._remember_remote_is_before((1, 13))
2392
return self._vfs_initialize(a_bzrdir, name=name)
2393
if response[0] != 'ok':
2394
raise errors.UnexpectedSmartServerResponse(response)
2395
# Turn the response into a RemoteRepository object.
2396
format = RemoteBranchFormat(network_name=response[1])
2397
repo_format = response_tuple_to_repo_format(response[3:])
2398
repo_path = response[2]
2399
if repository is not None:
2400
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2401
url_diff = urlutils.relative_url(repository.user_url,
2404
raise AssertionError(
2405
'repository.user_url %r does not match URL from server '
2406
'response (%r + %r)'
2407
% (repository.user_url, a_bzrdir.user_url, repo_path))
2408
remote_repo = repository
2411
repo_bzrdir = a_bzrdir
2413
repo_bzrdir = RemoteBzrDir(
2414
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2416
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2417
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2418
format=format, setup_stacking=False, name=name)
2419
# XXX: We know this is a new branch, so it must have revno 0, revid
2420
# NULL_REVISION. Creating the branch locked would make this be unable
2421
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2422
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2423
return remote_branch
2425
def make_tags(self, branch):
2427
return self._custom_format.make_tags(branch)
2429
def supports_tags(self):
2430
# Remote branches might support tags, but we won't know until we
2431
# access the real remote branch.
2433
return self._custom_format.supports_tags()
2435
def supports_stacking(self):
2437
return self._custom_format.supports_stacking()
2439
def supports_set_append_revisions_only(self):
2441
return self._custom_format.supports_set_append_revisions_only()
2443
def _use_default_local_heads_to_fetch(self):
2444
# If the branch format is a metadir format *and* its heads_to_fetch
2445
# implementation is not overridden vs the base class, we can use the
2446
# base class logic rather than use the heads_to_fetch RPC. This is
2447
# usually cheaper in terms of net round trips, as the last-revision and
2448
# tags info fetched is cached and would be fetched anyway.
2450
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2451
branch_class = self._custom_format._branch_class()
2452
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2453
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2457
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2458
"""Branch stored on a server accessed by HPSS RPC.
2460
At the moment most operations are mapped down to simple file operations.
2463
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2464
_client=None, format=None, setup_stacking=True, name=None):
2465
"""Create a RemoteBranch instance.
2467
:param real_branch: An optional local implementation of the branch
2468
format, usually accessing the data via the VFS.
2469
:param _client: Private parameter for testing.
2470
:param format: A RemoteBranchFormat object, None to create one
2471
automatically. If supplied it should have a network_name already
2473
:param setup_stacking: If True make an RPC call to determine the
2474
stacked (or not) status of the branch. If False assume the branch
2476
:param name: Colocated branch name
2478
# We intentionally don't call the parent class's __init__, because it
2479
# will try to assign to self.tags, which is a property in this subclass.
2480
# And the parent's __init__ doesn't do much anyway.
2481
self.bzrdir = remote_bzrdir
2482
if _client is not None:
2483
self._client = _client
2485
self._client = remote_bzrdir._client
2486
self.repository = remote_repository
2487
if real_branch is not None:
2488
self._real_branch = real_branch
2489
# Give the remote repository the matching real repo.
2490
real_repo = self._real_branch.repository
2491
if isinstance(real_repo, RemoteRepository):
2492
real_repo._ensure_real()
2493
real_repo = real_repo._real_repository
2494
self.repository._set_real_repository(real_repo)
2495
# Give the branch the remote repository to let fast-pathing happen.
2496
self._real_branch.repository = self.repository
2498
self._real_branch = None
2499
# Fill out expected attributes of branch for bzrlib API users.
2500
self._clear_cached_state()
2501
# TODO: deprecate self.base in favor of user_url
2502
self.base = self.bzrdir.user_url
2504
self._control_files = None
2505
self._lock_mode = None
2506
self._lock_token = None
2507
self._repo_lock_token = None
2508
self._lock_count = 0
2509
self._leave_lock = False
2510
# Setup a format: note that we cannot call _ensure_real until all the
2511
# attributes above are set: This code cannot be moved higher up in this
2514
self._format = RemoteBranchFormat()
2515
if real_branch is not None:
2516
self._format._network_name = \
2517
self._real_branch._format.network_name()
2519
self._format = format
2520
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2521
# branch.open_branch method.
2522
self._real_ignore_fallbacks = not setup_stacking
2523
if not self._format._network_name:
2524
# Did not get from open_branchV2 - old server.
2526
self._format._network_name = \
2527
self._real_branch._format.network_name()
2528
self.tags = self._format.make_tags(self)
2529
# The base class init is not called, so we duplicate this:
2530
hooks = branch.Branch.hooks['open']
2533
self._is_stacked = False
2535
self._setup_stacking()
2537
def _setup_stacking(self):
2538
# configure stacking into the remote repository, by reading it from
2541
fallback_url = self.get_stacked_on_url()
2542
except (errors.NotStacked, errors.UnstackableBranchFormat,
2543
errors.UnstackableRepositoryFormat), e:
2545
self._is_stacked = True
2546
self._activate_fallback_location(fallback_url)
2548
def _get_config(self):
2549
return RemoteBranchConfig(self)
2551
def _get_real_transport(self):
2552
# if we try vfs access, return the real branch's vfs transport
2554
return self._real_branch._transport
2556
_transport = property(_get_real_transport)
2559
return "%s(%s)" % (self.__class__.__name__, self.base)
2563
def _ensure_real(self):
2564
"""Ensure that there is a _real_branch set.
2566
Used before calls to self._real_branch.
2568
if self._real_branch is None:
2569
if not vfs.vfs_enabled():
2570
raise AssertionError('smart server vfs must be enabled '
2571
'to use vfs implementation')
2572
self.bzrdir._ensure_real()
2573
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2574
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2575
if self.repository._real_repository is None:
2576
# Give the remote repository the matching real repo.
2577
real_repo = self._real_branch.repository
2578
if isinstance(real_repo, RemoteRepository):
2579
real_repo._ensure_real()
2580
real_repo = real_repo._real_repository
2581
self.repository._set_real_repository(real_repo)
2582
# Give the real branch the remote repository to let fast-pathing
2584
self._real_branch.repository = self.repository
2585
if self._lock_mode == 'r':
2586
self._real_branch.lock_read()
2587
elif self._lock_mode == 'w':
2588
self._real_branch.lock_write(token=self._lock_token)
2590
def _translate_error(self, err, **context):
2591
self.repository._translate_error(err, branch=self, **context)
2593
def _clear_cached_state(self):
2594
super(RemoteBranch, self)._clear_cached_state()
2595
if self._real_branch is not None:
2596
self._real_branch._clear_cached_state()
2598
def _clear_cached_state_of_remote_branch_only(self):
2599
"""Like _clear_cached_state, but doesn't clear the cache of
2602
This is useful when falling back to calling a method of
2603
self._real_branch that changes state. In that case the underlying
2604
branch changes, so we need to invalidate this RemoteBranch's cache of
2605
it. However, there's no need to invalidate the _real_branch's cache
2606
too, in fact doing so might harm performance.
2608
super(RemoteBranch, self)._clear_cached_state()
2611
def control_files(self):
2612
# Defer actually creating RemoteBranchLockableFiles until its needed,
2613
# because it triggers an _ensure_real that we otherwise might not need.
2614
if self._control_files is None:
2615
self._control_files = RemoteBranchLockableFiles(
2616
self.bzrdir, self._client)
2617
return self._control_files
2619
def _get_checkout_format(self):
2621
return self._real_branch._get_checkout_format()
2623
def get_physical_lock_status(self):
2624
"""See Branch.get_physical_lock_status()."""
2625
# should be an API call to the server, as branches must be lockable.
2627
return self._real_branch.get_physical_lock_status()
2629
def get_stacked_on_url(self):
2630
"""Get the URL this branch is stacked against.
2632
:raises NotStacked: If the branch is not stacked.
2633
:raises UnstackableBranchFormat: If the branch does not support
2635
:raises UnstackableRepositoryFormat: If the repository does not support
2639
# there may not be a repository yet, so we can't use
2640
# self._translate_error, so we can't use self._call either.
2641
response = self._client.call('Branch.get_stacked_on_url',
2642
self._remote_path())
2643
except errors.ErrorFromSmartServer, err:
2644
# there may not be a repository yet, so we can't call through
2645
# its _translate_error
2646
_translate_error(err, branch=self)
2647
except errors.UnknownSmartMethod, err:
2649
return self._real_branch.get_stacked_on_url()
2650
if response[0] != 'ok':
2651
raise errors.UnexpectedSmartServerResponse(response)
2654
def set_stacked_on_url(self, url):
2655
branch.Branch.set_stacked_on_url(self, url)
2657
self._is_stacked = False
2659
self._is_stacked = True
2661
def _vfs_get_tags_bytes(self):
2663
return self._real_branch._get_tags_bytes()
2666
def _get_tags_bytes(self):
2667
if self._tags_bytes is None:
2668
self._tags_bytes = self._get_tags_bytes_via_hpss()
2669
return self._tags_bytes
2671
def _get_tags_bytes_via_hpss(self):
2672
medium = self._client._medium
2673
if medium._is_remote_before((1, 13)):
2674
return self._vfs_get_tags_bytes()
2676
response = self._call('Branch.get_tags_bytes', self._remote_path())
2677
except errors.UnknownSmartMethod:
2678
medium._remember_remote_is_before((1, 13))
2679
return self._vfs_get_tags_bytes()
2682
def _vfs_set_tags_bytes(self, bytes):
2684
return self._real_branch._set_tags_bytes(bytes)
2686
def _set_tags_bytes(self, bytes):
2687
if self.is_locked():
2688
self._tags_bytes = bytes
2689
medium = self._client._medium
2690
if medium._is_remote_before((1, 18)):
2691
self._vfs_set_tags_bytes(bytes)
2695
self._remote_path(), self._lock_token, self._repo_lock_token)
2696
response = self._call_with_body_bytes(
2697
'Branch.set_tags_bytes', args, bytes)
2698
except errors.UnknownSmartMethod:
2699
medium._remember_remote_is_before((1, 18))
2700
self._vfs_set_tags_bytes(bytes)
2702
def lock_read(self):
2703
"""Lock the branch for read operations.
2705
:return: A bzrlib.lock.LogicalLockResult.
2707
self.repository.lock_read()
2708
if not self._lock_mode:
2709
self._note_lock('r')
2710
self._lock_mode = 'r'
2711
self._lock_count = 1
2712
if self._real_branch is not None:
2713
self._real_branch.lock_read()
2715
self._lock_count += 1
2716
return lock.LogicalLockResult(self.unlock)
2718
def _remote_lock_write(self, token):
2720
branch_token = repo_token = ''
2722
branch_token = token
2723
repo_token = self.repository.lock_write().repository_token
2724
self.repository.unlock()
2725
err_context = {'token': token}
2727
response = self._call(
2728
'Branch.lock_write', self._remote_path(), branch_token,
2729
repo_token or '', **err_context)
2730
except errors.LockContention, e:
2731
# The LockContention from the server doesn't have any
2732
# information about the lock_url. We re-raise LockContention
2733
# with valid lock_url.
2734
raise errors.LockContention('(remote lock)',
2735
self.repository.base.split('.bzr/')[0])
2736
if response[0] != 'ok':
2737
raise errors.UnexpectedSmartServerResponse(response)
2738
ok, branch_token, repo_token = response
2739
return branch_token, repo_token
2741
def lock_write(self, token=None):
2742
if not self._lock_mode:
2743
self._note_lock('w')
2744
# Lock the branch and repo in one remote call.
2745
remote_tokens = self._remote_lock_write(token)
2746
self._lock_token, self._repo_lock_token = remote_tokens
2747
if not self._lock_token:
2748
raise SmartProtocolError('Remote server did not return a token!')
2749
# Tell the self.repository object that it is locked.
2750
self.repository.lock_write(
2751
self._repo_lock_token, _skip_rpc=True)
2753
if self._real_branch is not None:
2754
self._real_branch.lock_write(token=self._lock_token)
2755
if token is not None:
2756
self._leave_lock = True
2758
self._leave_lock = False
2759
self._lock_mode = 'w'
2760
self._lock_count = 1
2761
elif self._lock_mode == 'r':
2762
raise errors.ReadOnlyError(self)
2764
if token is not None:
2765
# A token was given to lock_write, and we're relocking, so
2766
# check that the given token actually matches the one we
2768
if token != self._lock_token:
2769
raise errors.TokenMismatch(token, self._lock_token)
2770
self._lock_count += 1
2771
# Re-lock the repository too.
2772
self.repository.lock_write(self._repo_lock_token)
2773
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2775
def _unlock(self, branch_token, repo_token):
2776
err_context = {'token': str((branch_token, repo_token))}
2777
response = self._call(
2778
'Branch.unlock', self._remote_path(), branch_token,
2779
repo_token or '', **err_context)
2780
if response == ('ok',):
2782
raise errors.UnexpectedSmartServerResponse(response)
2784
@only_raises(errors.LockNotHeld, errors.LockBroken)
2787
self._lock_count -= 1
2788
if not self._lock_count:
2789
self._clear_cached_state()
2790
mode = self._lock_mode
2791
self._lock_mode = None
2792
if self._real_branch is not None:
2793
if (not self._leave_lock and mode == 'w' and
2794
self._repo_lock_token):
2795
# If this RemoteBranch will remove the physical lock
2796
# for the repository, make sure the _real_branch
2797
# doesn't do it first. (Because the _real_branch's
2798
# repository is set to be the RemoteRepository.)
2799
self._real_branch.repository.leave_lock_in_place()
2800
self._real_branch.unlock()
2802
# Only write-locked branched need to make a remote method
2803
# call to perform the unlock.
2805
if not self._lock_token:
2806
raise AssertionError('Locked, but no token!')
2807
branch_token = self._lock_token
2808
repo_token = self._repo_lock_token
2809
self._lock_token = None
2810
self._repo_lock_token = None
2811
if not self._leave_lock:
2812
self._unlock(branch_token, repo_token)
2814
self.repository.unlock()
2816
def break_lock(self):
2818
return self._real_branch.break_lock()
2820
def leave_lock_in_place(self):
2821
if not self._lock_token:
2822
raise NotImplementedError(self.leave_lock_in_place)
2823
self._leave_lock = True
2825
def dont_leave_lock_in_place(self):
2826
if not self._lock_token:
2827
raise NotImplementedError(self.dont_leave_lock_in_place)
2828
self._leave_lock = False
2831
def get_rev_id(self, revno, history=None):
2833
return _mod_revision.NULL_REVISION
2834
last_revision_info = self.last_revision_info()
2835
ok, result = self.repository.get_rev_id_for_revno(
2836
revno, last_revision_info)
2839
missing_parent = result[1]
2840
# Either the revision named by the server is missing, or its parent
2841
# is. Call get_parent_map to determine which, so that we report a
2843
parent_map = self.repository.get_parent_map([missing_parent])
2844
if missing_parent in parent_map:
2845
missing_parent = parent_map[missing_parent]
2846
raise errors.RevisionNotPresent(missing_parent, self.repository)
2848
def _last_revision_info(self):
2849
response = self._call('Branch.last_revision_info', self._remote_path())
2850
if response[0] != 'ok':
2851
raise SmartProtocolError('unexpected response code %s' % (response,))
2852
revno = int(response[1])
2853
last_revision = response[2]
2854
return (revno, last_revision)
2856
def _gen_revision_history(self):
2857
"""See Branch._gen_revision_history()."""
2858
if self._is_stacked:
2860
return self._real_branch._gen_revision_history()
2861
response_tuple, response_handler = self._call_expecting_body(
2862
'Branch.revision_history', self._remote_path())
2863
if response_tuple[0] != 'ok':
2864
raise errors.UnexpectedSmartServerResponse(response_tuple)
2865
result = response_handler.read_body_bytes().split('\x00')
2870
def _remote_path(self):
2871
return self.bzrdir._path_for_remote_call(self._client)
2873
def _set_last_revision_descendant(self, revision_id, other_branch,
2874
allow_diverged=False, allow_overwrite_descendant=False):
2875
# This performs additional work to meet the hook contract; while its
2876
# undesirable, we have to synthesise the revno to call the hook, and
2877
# not calling the hook is worse as it means changes can't be prevented.
2878
# Having calculated this though, we can't just call into
2879
# set_last_revision_info as a simple call, because there is a set_rh
2880
# hook that some folk may still be using.
2881
old_revno, old_revid = self.last_revision_info()
2882
history = self._lefthand_history(revision_id)
2883
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2884
err_context = {'other_branch': other_branch}
2885
response = self._call('Branch.set_last_revision_ex',
2886
self._remote_path(), self._lock_token, self._repo_lock_token,
2887
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2889
self._clear_cached_state()
2890
if len(response) != 3 and response[0] != 'ok':
2891
raise errors.UnexpectedSmartServerResponse(response)
2892
new_revno, new_revision_id = response[1:]
2893
self._last_revision_info_cache = new_revno, new_revision_id
2894
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2895
if self._real_branch is not None:
2896
cache = new_revno, new_revision_id
2897
self._real_branch._last_revision_info_cache = cache
2899
def _set_last_revision(self, revision_id):
2900
old_revno, old_revid = self.last_revision_info()
2901
# This performs additional work to meet the hook contract; while its
2902
# undesirable, we have to synthesise the revno to call the hook, and
2903
# not calling the hook is worse as it means changes can't be prevented.
2904
# Having calculated this though, we can't just call into
2905
# set_last_revision_info as a simple call, because there is a set_rh
2906
# hook that some folk may still be using.
2907
history = self._lefthand_history(revision_id)
2908
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2909
self._clear_cached_state()
2910
response = self._call('Branch.set_last_revision',
2911
self._remote_path(), self._lock_token, self._repo_lock_token,
2913
if response != ('ok',):
2914
raise errors.UnexpectedSmartServerResponse(response)
2915
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2918
def set_revision_history(self, rev_history):
2919
# Send just the tip revision of the history; the server will generate
2920
# the full history from that. If the revision doesn't exist in this
2921
# branch, NoSuchRevision will be raised.
2922
if rev_history == []:
2925
rev_id = rev_history[-1]
2926
self._set_last_revision(rev_id)
2927
for hook in branch.Branch.hooks['set_rh']:
2928
hook(self, rev_history)
2929
self._cache_revision_history(rev_history)
2931
def _get_parent_location(self):
2932
medium = self._client._medium
2933
if medium._is_remote_before((1, 13)):
2934
return self._vfs_get_parent_location()
2936
response = self._call('Branch.get_parent', self._remote_path())
2937
except errors.UnknownSmartMethod:
2938
medium._remember_remote_is_before((1, 13))
2939
return self._vfs_get_parent_location()
2940
if len(response) != 1:
2941
raise errors.UnexpectedSmartServerResponse(response)
2942
parent_location = response[0]
2943
if parent_location == '':
2945
return parent_location
2947
def _vfs_get_parent_location(self):
2949
return self._real_branch._get_parent_location()
2951
def _set_parent_location(self, url):
2952
medium = self._client._medium
2953
if medium._is_remote_before((1, 15)):
2954
return self._vfs_set_parent_location(url)
2956
call_url = url or ''
2957
if type(call_url) is not str:
2958
raise AssertionError('url must be a str or None (%s)' % url)
2959
response = self._call('Branch.set_parent_location',
2960
self._remote_path(), self._lock_token, self._repo_lock_token,
2962
except errors.UnknownSmartMethod:
2963
medium._remember_remote_is_before((1, 15))
2964
return self._vfs_set_parent_location(url)
2966
raise errors.UnexpectedSmartServerResponse(response)
2968
def _vfs_set_parent_location(self, url):
2970
return self._real_branch._set_parent_location(url)
2973
def pull(self, source, overwrite=False, stop_revision=None,
2975
self._clear_cached_state_of_remote_branch_only()
2977
return self._real_branch.pull(
2978
source, overwrite=overwrite, stop_revision=stop_revision,
2979
_override_hook_target=self, **kwargs)
2982
def push(self, target, overwrite=False, stop_revision=None):
2984
return self._real_branch.push(
2985
target, overwrite=overwrite, stop_revision=stop_revision,
2986
_override_hook_source_branch=self)
2988
def is_locked(self):
2989
return self._lock_count >= 1
2992
def revision_id_to_revno(self, revision_id):
2994
return self._real_branch.revision_id_to_revno(revision_id)
2997
def set_last_revision_info(self, revno, revision_id):
2998
# XXX: These should be returned by the set_last_revision_info verb
2999
old_revno, old_revid = self.last_revision_info()
3000
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3001
revision_id = _mod_revision.ensure_null(revision_id)
3003
response = self._call('Branch.set_last_revision_info',
3004
self._remote_path(), self._lock_token, self._repo_lock_token,
3005
str(revno), revision_id)
3006
except errors.UnknownSmartMethod:
3008
self._clear_cached_state_of_remote_branch_only()
3009
self._real_branch.set_last_revision_info(revno, revision_id)
3010
self._last_revision_info_cache = revno, revision_id
3012
if response == ('ok',):
3013
self._clear_cached_state()
3014
self._last_revision_info_cache = revno, revision_id
3015
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3016
# Update the _real_branch's cache too.
3017
if self._real_branch is not None:
3018
cache = self._last_revision_info_cache
3019
self._real_branch._last_revision_info_cache = cache
3021
raise errors.UnexpectedSmartServerResponse(response)
3024
def generate_revision_history(self, revision_id, last_rev=None,
3026
medium = self._client._medium
3027
if not medium._is_remote_before((1, 6)):
3028
# Use a smart method for 1.6 and above servers
3030
self._set_last_revision_descendant(revision_id, other_branch,
3031
allow_diverged=True, allow_overwrite_descendant=True)
3033
except errors.UnknownSmartMethod:
3034
medium._remember_remote_is_before((1, 6))
3035
self._clear_cached_state_of_remote_branch_only()
3036
self.set_revision_history(self._lefthand_history(revision_id,
3037
last_rev=last_rev,other_branch=other_branch))
3039
def set_push_location(self, location):
3041
return self._real_branch.set_push_location(location)
3043
def heads_to_fetch(self):
3044
if self._format._use_default_local_heads_to_fetch():
3045
# We recognise this format, and its heads-to-fetch implementation
3046
# is the default one (tip + tags). In this case it's cheaper to
3047
# just use the default implementation rather than a special RPC as
3048
# the tip and tags data is cached.
3049
return branch.Branch.heads_to_fetch(self)
3050
medium = self._client._medium
3051
if medium._is_remote_before((2, 4)):
3052
return self._vfs_heads_to_fetch()
3054
return self._rpc_heads_to_fetch()
3055
except errors.UnknownSmartMethod:
3056
medium._remember_remote_is_before((2, 4))
3057
return self._vfs_heads_to_fetch()
3059
def _rpc_heads_to_fetch(self):
3060
response = self._call('Branch.heads_to_fetch', self._remote_path())
3061
if len(response) != 2:
3062
raise errors.UnexpectedSmartServerResponse(response)
3063
must_fetch, if_present_fetch = response
3064
return set(must_fetch), set(if_present_fetch)
3066
def _vfs_heads_to_fetch(self):
3068
return self._real_branch.heads_to_fetch()
3071
class RemoteConfig(object):
3072
"""A Config that reads and writes from smart verbs.
3074
It is a low-level object that considers config data to be name/value pairs
3075
that may be associated with a section. Assigning meaning to the these
3076
values is done at higher levels like bzrlib.config.TreeConfig.
3079
def get_option(self, name, section=None, default=None):
3080
"""Return the value associated with a named option.
3082
:param name: The name of the value
3083
:param section: The section the option is in (if any)
3084
:param default: The value to return if the value is not set
3085
:return: The value or default value
3088
configobj = self._get_configobj()
3090
section_obj = configobj
3093
section_obj = configobj[section]
3096
return section_obj.get(name, default)
3097
except errors.UnknownSmartMethod:
3098
return self._vfs_get_option(name, section, default)
3100
def _response_to_configobj(self, response):
3101
if len(response[0]) and response[0][0] != 'ok':
3102
raise errors.UnexpectedSmartServerResponse(response)
3103
lines = response[1].read_body_bytes().splitlines()
3104
return config.ConfigObj(lines, encoding='utf-8')
3107
class RemoteBranchConfig(RemoteConfig):
3108
"""A RemoteConfig for Branches."""
3110
def __init__(self, branch):
3111
self._branch = branch
3113
def _get_configobj(self):
3114
path = self._branch._remote_path()
3115
response = self._branch._client.call_expecting_body(
3116
'Branch.get_config_file', path)
3117
return self._response_to_configobj(response)
3119
def set_option(self, value, name, section=None):
3120
"""Set the value associated with a named option.
3122
:param value: The value to set
3123
:param name: The name of the value to set
3124
:param section: The section the option is in (if any)
3126
medium = self._branch._client._medium
3127
if medium._is_remote_before((1, 14)):
3128
return self._vfs_set_option(value, name, section)
3129
if isinstance(value, dict):
3130
if medium._is_remote_before((2, 2)):
3131
return self._vfs_set_option(value, name, section)
3132
return self._set_config_option_dict(value, name, section)
3134
return self._set_config_option(value, name, section)
3136
def _set_config_option(self, value, name, section):
3138
path = self._branch._remote_path()
3139
response = self._branch._client.call('Branch.set_config_option',
3140
path, self._branch._lock_token, self._branch._repo_lock_token,
3141
value.encode('utf8'), name, section or '')
3142
except errors.UnknownSmartMethod:
3143
medium = self._branch._client._medium
3144
medium._remember_remote_is_before((1, 14))
3145
return self._vfs_set_option(value, name, section)
3147
raise errors.UnexpectedSmartServerResponse(response)
3149
def _serialize_option_dict(self, option_dict):
3151
for key, value in option_dict.items():
3152
if isinstance(key, unicode):
3153
key = key.encode('utf8')
3154
if isinstance(value, unicode):
3155
value = value.encode('utf8')
3156
utf8_dict[key] = value
3157
return bencode.bencode(utf8_dict)
3159
def _set_config_option_dict(self, value, name, section):
3161
path = self._branch._remote_path()
3162
serialised_dict = self._serialize_option_dict(value)
3163
response = self._branch._client.call(
3164
'Branch.set_config_option_dict',
3165
path, self._branch._lock_token, self._branch._repo_lock_token,
3166
serialised_dict, name, section or '')
3167
except errors.UnknownSmartMethod:
3168
medium = self._branch._client._medium
3169
medium._remember_remote_is_before((2, 2))
3170
return self._vfs_set_option(value, name, section)
3172
raise errors.UnexpectedSmartServerResponse(response)
3174
def _real_object(self):
3175
self._branch._ensure_real()
3176
return self._branch._real_branch
3178
def _vfs_set_option(self, value, name, section=None):
3179
return self._real_object()._get_config().set_option(
3180
value, name, section)
3183
class RemoteBzrDirConfig(RemoteConfig):
3184
"""A RemoteConfig for BzrDirs."""
3186
def __init__(self, bzrdir):
3187
self._bzrdir = bzrdir
3189
def _get_configobj(self):
3190
medium = self._bzrdir._client._medium
3191
verb = 'BzrDir.get_config_file'
3192
if medium._is_remote_before((1, 15)):
3193
raise errors.UnknownSmartMethod(verb)
3194
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3195
response = self._bzrdir._call_expecting_body(
3197
return self._response_to_configobj(response)
3199
def _vfs_get_option(self, name, section, default):
3200
return self._real_object()._get_config().get_option(
3201
name, section, default)
3203
def set_option(self, value, name, section=None):
3204
"""Set the value associated with a named option.
3206
:param value: The value to set
3207
:param name: The name of the value to set
3208
:param section: The section the option is in (if any)
3210
return self._real_object()._get_config().set_option(
3211
value, name, section)
3213
def _real_object(self):
3214
self._bzrdir._ensure_real()
3215
return self._bzrdir._real_bzrdir
3219
def _extract_tar(tar, to_dir):
3220
"""Extract all the contents of a tarfile object.
3222
A replacement for extractall, which is not present in python2.4
3225
tar.extract(tarinfo, to_dir)
3228
def _translate_error(err, **context):
3229
"""Translate an ErrorFromSmartServer into a more useful error.
3231
Possible context keys:
3239
If the error from the server doesn't match a known pattern, then
3240
UnknownErrorFromSmartServer is raised.
3244
return context[name]
3245
except KeyError, key_err:
3246
mutter('Missing key %r in context %r', key_err.args[0], context)
3249
"""Get the path from the context if present, otherwise use first error
3253
return context['path']
3254
except KeyError, key_err:
3256
return err.error_args[0]
3257
except IndexError, idx_err:
3259
'Missing key %r in context %r', key_err.args[0], context)
3262
if err.error_verb == 'NoSuchRevision':
3263
raise NoSuchRevision(find('branch'), err.error_args[0])
3264
elif err.error_verb == 'nosuchrevision':
3265
raise NoSuchRevision(find('repository'), err.error_args[0])
3266
elif err.error_verb == 'nobranch':
3267
if len(err.error_args) >= 1:
3268
extra = err.error_args[0]
3271
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3273
elif err.error_verb == 'norepository':
3274
raise errors.NoRepositoryPresent(find('bzrdir'))
3275
elif err.error_verb == 'UnlockableTransport':
3276
raise errors.UnlockableTransport(find('bzrdir').root_transport)
3277
elif err.error_verb == 'TokenMismatch':
3278
raise errors.TokenMismatch(find('token'), '(remote token)')
3279
elif err.error_verb == 'Diverged':
3280
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3281
elif err.error_verb == 'NotStacked':
3282
raise errors.NotStacked(branch=find('branch'))
3283
elif err.error_verb == 'PermissionDenied':
3285
if len(err.error_args) >= 2:
3286
extra = err.error_args[1]
3289
raise errors.PermissionDenied(path, extra=extra)
3290
elif err.error_verb == 'ReadError':
3292
raise errors.ReadError(path)
3293
elif err.error_verb == 'NoSuchFile':
3295
raise errors.NoSuchFile(path)
3296
_translate_error_without_context(err)
3299
def _translate_error_without_context(err):
3300
"""Translate any ErrorFromSmartServer values that don't require context"""
3301
if err.error_verb == 'IncompatibleRepositories':
3302
raise errors.IncompatibleRepositories(err.error_args[0],
3303
err.error_args[1], err.error_args[2])
3304
elif err.error_verb == 'LockContention':
3305
raise errors.LockContention('(remote lock)')
3306
elif err.error_verb == 'LockFailed':
3307
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3308
elif err.error_verb == 'TipChangeRejected':
3309
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3310
elif err.error_verb == 'UnstackableBranchFormat':
3311
raise errors.UnstackableBranchFormat(*err.error_args)
3312
elif err.error_verb == 'UnstackableRepositoryFormat':
3313
raise errors.UnstackableRepositoryFormat(*err.error_args)
3314
elif err.error_verb == 'FileExists':
3315
raise errors.FileExists(err.error_args[0])
3316
elif err.error_verb == 'DirectoryNotEmpty':
3317
raise errors.DirectoryNotEmpty(err.error_args[0])
3318
elif err.error_verb == 'ShortReadvError':
3319
args = err.error_args
3320
raise errors.ShortReadvError(
3321
args[0], int(args[1]), int(args[2]), int(args[3]))
3322
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3323
encoding = str(err.error_args[0]) # encoding must always be a string
3324
val = err.error_args[1]
3325
start = int(err.error_args[2])
3326
end = int(err.error_args[3])
3327
reason = str(err.error_args[4]) # reason must always be a string
3328
if val.startswith('u:'):
3329
val = val[2:].decode('utf-8')
3330
elif val.startswith('s:'):
3331
val = val[2:].decode('base64')
3332
if err.error_verb == 'UnicodeDecodeError':
3333
raise UnicodeDecodeError(encoding, val, start, end, reason)
3334
elif err.error_verb == 'UnicodeEncodeError':
3335
raise UnicodeEncodeError(encoding, val, start, end, reason)
3336
elif err.error_verb == 'ReadOnlyError':
3337
raise errors.TransportNotPossible('readonly transport')
3338
elif err.error_verb == 'MemoryError':
3339
raise errors.BzrError("remote server out of memory\n"
3340
"Retry non-remotely, or contact the server admin for details.")
3341
raise errors.UnknownErrorFromSmartServer(err)