1
# Copyright (C) 2006-2012 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
17
from __future__ import absolute_import
25
config as _mod_config,
35
repository as _mod_repository,
36
revision as _mod_revision,
38
testament as _mod_testament,
43
bzrdir as _mod_bzrdir,
48
from .bzr.branch import BranchReferenceFormat
49
from .branch import BranchWriteLockResult
50
from .decorators import needs_read_lock, needs_write_lock, only_raises
55
from .i18n import gettext
56
from .bzr.inventory import Inventory
57
from .lockable_files import LockableFiles
62
from .smart import client, vfs, repository as smart_repo
63
from .smart.client import _SmartClient
64
from .revision import NULL_REVISION
65
from .revisiontree import InventoryRevisionTree
66
from .repository import RepositoryWriteLockResult, _LazyListJoin
67
from .serializer import format_registry as serializer_format_registry
68
from .trace import mutter, note, warning, log_exception_quietly
69
from .bzr.versionedfile import FulltextContentFactory
72
_DEFAULT_SEARCH_DEPTH = 100
75
class _RpcHelper(object):
76
"""Mixin class that helps with issuing RPCs."""
78
def _call(self, method, *args, **err_context):
80
return self._client.call(method, *args)
81
except errors.ErrorFromSmartServer as err:
82
self._translate_error(err, **err_context)
84
def _call_expecting_body(self, method, *args, **err_context):
86
return self._client.call_expecting_body(method, *args)
87
except errors.ErrorFromSmartServer as err:
88
self._translate_error(err, **err_context)
90
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
92
return self._client.call_with_body_bytes(method, args, body_bytes)
93
except errors.ErrorFromSmartServer as err:
94
self._translate_error(err, **err_context)
96
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
99
return self._client.call_with_body_bytes_expecting_body(
100
method, args, body_bytes)
101
except errors.ErrorFromSmartServer as err:
102
self._translate_error(err, **err_context)
105
def response_tuple_to_repo_format(response):
106
"""Convert a response tuple describing a repository format to a format."""
107
format = RemoteRepositoryFormat()
108
format._rich_root_data = (response[0] == 'yes')
109
format._supports_tree_reference = (response[1] == 'yes')
110
format._supports_external_lookups = (response[2] == 'yes')
111
format._network_name = response[3]
115
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.remote
116
# does not have to be imported unless a remote format is involved.
118
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
119
"""Format representing bzrdirs accessed via a smart server"""
121
supports_workingtrees = False
123
colocated_branches = False
126
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
127
# XXX: It's a bit ugly that the network name is here, because we'd
128
# like to believe that format objects are stateless or at least
129
# immutable, However, we do at least avoid mutating the name after
130
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
131
self._network_name = None
134
return "%s(_network_name=%r)" % (self.__class__.__name__,
137
def get_format_description(self):
138
if self._network_name:
140
real_format = controldir.network_format_registry.get(
145
return 'Remote: ' + real_format.get_format_description()
146
return 'bzr remote bzrdir'
148
def get_format_string(self):
149
raise NotImplementedError(self.get_format_string)
151
def network_name(self):
152
if self._network_name:
153
return self._network_name
155
raise AssertionError("No network name set.")
157
def initialize_on_transport(self, transport):
159
# hand off the request to the smart server
160
client_medium = transport.get_smart_medium()
161
except errors.NoSmartMedium:
162
# TODO: lookup the local format from a server hint.
163
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
164
return local_dir_format.initialize_on_transport(transport)
165
client = _SmartClient(client_medium)
166
path = client.remote_path_from_transport(transport)
168
response = client.call('BzrDirFormat.initialize', path)
169
except errors.ErrorFromSmartServer as err:
170
_translate_error(err, path=path)
171
if response[0] != 'ok':
172
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
173
format = RemoteBzrDirFormat()
174
self._supply_sub_formats_to(format)
175
return RemoteBzrDir(transport, format)
177
def parse_NoneTrueFalse(self, arg):
184
raise AssertionError("invalid arg %r" % arg)
186
def _serialize_NoneTrueFalse(self, arg):
193
def _serialize_NoneString(self, arg):
196
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
197
create_prefix=False, force_new_repo=False, stacked_on=None,
198
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
201
# hand off the request to the smart server
202
client_medium = transport.get_smart_medium()
203
except errors.NoSmartMedium:
206
# Decline to open it if the server doesn't support our required
207
# version (3) so that the VFS-based transport will do it.
208
if client_medium.should_probe():
210
server_version = client_medium.protocol_version()
211
if server_version != '2':
215
except errors.SmartProtocolError:
216
# Apparently there's no usable smart server there, even though
217
# the medium supports the smart protocol.
222
client = _SmartClient(client_medium)
223
path = client.remote_path_from_transport(transport)
224
if client_medium._is_remote_before((1, 16)):
227
# TODO: lookup the local format from a server hint.
228
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
229
self._supply_sub_formats_to(local_dir_format)
230
return local_dir_format.initialize_on_transport_ex(transport,
231
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
232
force_new_repo=force_new_repo, stacked_on=stacked_on,
233
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
234
make_working_trees=make_working_trees, shared_repo=shared_repo,
236
return self._initialize_on_transport_ex_rpc(client, path, transport,
237
use_existing_dir, create_prefix, force_new_repo, stacked_on,
238
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
240
def _initialize_on_transport_ex_rpc(self, client, path, transport,
241
use_existing_dir, create_prefix, force_new_repo, stacked_on,
242
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
244
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
245
args.append(self._serialize_NoneTrueFalse(create_prefix))
246
args.append(self._serialize_NoneTrueFalse(force_new_repo))
247
args.append(self._serialize_NoneString(stacked_on))
248
# stack_on_pwd is often/usually our transport
251
stack_on_pwd = transport.relpath(stack_on_pwd)
254
except errors.PathNotChild:
256
args.append(self._serialize_NoneString(stack_on_pwd))
257
args.append(self._serialize_NoneString(repo_format_name))
258
args.append(self._serialize_NoneTrueFalse(make_working_trees))
259
args.append(self._serialize_NoneTrueFalse(shared_repo))
260
request_network_name = self._network_name or \
261
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
263
response = client.call('BzrDirFormat.initialize_ex_1.16',
264
request_network_name, path, *args)
265
except errors.UnknownSmartMethod:
266
client._medium._remember_remote_is_before((1,16))
267
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
268
self._supply_sub_formats_to(local_dir_format)
269
return local_dir_format.initialize_on_transport_ex(transport,
270
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
271
force_new_repo=force_new_repo, stacked_on=stacked_on,
272
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
273
make_working_trees=make_working_trees, shared_repo=shared_repo,
275
except errors.ErrorFromSmartServer as err:
276
_translate_error(err, path=path)
277
repo_path = response[0]
278
bzrdir_name = response[6]
279
require_stacking = response[7]
280
require_stacking = self.parse_NoneTrueFalse(require_stacking)
281
format = RemoteBzrDirFormat()
282
format._network_name = bzrdir_name
283
self._supply_sub_formats_to(format)
284
bzrdir = RemoteBzrDir(transport, format, _client=client)
286
repo_format = response_tuple_to_repo_format(response[1:])
290
repo_bzrdir_format = RemoteBzrDirFormat()
291
repo_bzrdir_format._network_name = response[5]
292
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
296
final_stack = response[8] or None
297
final_stack_pwd = response[9] or None
299
final_stack_pwd = urlutils.join(
300
transport.base, final_stack_pwd)
301
remote_repo = RemoteRepository(repo_bzr, repo_format)
302
if len(response) > 10:
303
# Updated server verb that locks remotely.
304
repo_lock_token = response[10] or None
305
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
307
remote_repo.dont_leave_lock_in_place()
309
remote_repo.lock_write()
310
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
311
final_stack_pwd, require_stacking)
312
policy.acquire_repository()
316
bzrdir._format.set_branch_format(self.get_branch_format())
318
# The repo has already been created, but we need to make sure that
319
# we'll make a stackable branch.
320
bzrdir._format.require_stacking(_skip_repo=True)
321
return remote_repo, bzrdir, require_stacking, policy
323
def _open(self, transport):
324
return RemoteBzrDir(transport, self)
326
def __eq__(self, other):
327
if not isinstance(other, RemoteBzrDirFormat):
329
return self.get_format_description() == other.get_format_description()
331
def __return_repository_format(self):
332
# Always return a RemoteRepositoryFormat object, but if a specific bzr
333
# repository format has been asked for, tell the RemoteRepositoryFormat
334
# that it should use that for init() etc.
335
result = RemoteRepositoryFormat()
336
custom_format = getattr(self, '_repository_format', None)
338
if isinstance(custom_format, RemoteRepositoryFormat):
341
# We will use the custom format to create repositories over the
342
# wire; expose its details like rich_root_data for code to
344
result._custom_format = custom_format
347
def get_branch_format(self):
348
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
349
if not isinstance(result, RemoteBranchFormat):
350
new_result = RemoteBranchFormat()
351
new_result._custom_format = result
353
self.set_branch_format(new_result)
357
repository_format = property(__return_repository_format,
358
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
361
class RemoteControlStore(_mod_config.IniFileStore):
362
"""Control store which attempts to use HPSS calls to retrieve control store.
364
Note that this is specific to bzr-based formats.
367
def __init__(self, bzrdir):
368
super(RemoteControlStore, self).__init__()
370
self._real_store = None
372
def lock_write(self, token=None):
374
return self._real_store.lock_write(token)
378
return self._real_store.unlock()
382
# We need to be able to override the undecorated implementation
383
self.save_without_locking()
385
def save_without_locking(self):
386
super(RemoteControlStore, self).save()
388
def _ensure_real(self):
389
self.bzrdir._ensure_real()
390
if self._real_store is None:
391
self._real_store = _mod_config.ControlStore(self.bzrdir)
393
def external_url(self):
394
return urlutils.join(self.branch.user_url, 'control.conf')
396
def _load_content(self):
397
medium = self.bzrdir._client._medium
398
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
400
response, handler = self.bzrdir._call_expecting_body(
401
'BzrDir.get_config_file', path)
402
except errors.UnknownSmartMethod:
404
return self._real_store._load_content()
405
if len(response) and response[0] != 'ok':
406
raise errors.UnexpectedSmartServerResponse(response)
407
return handler.read_body_bytes()
409
def _save_content(self, content):
410
# FIXME JRV 2011-11-22: Ideally this should use a
411
# HPSS call too, but at the moment it is not possible
412
# to write lock control directories.
414
return self._real_store._save_content(content)
417
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
418
"""Control directory on a remote server, accessed via bzr:// or similar."""
420
def __init__(self, transport, format, _client=None, _force_probe=False):
421
"""Construct a RemoteBzrDir.
423
:param _client: Private parameter for testing. Disables probing and the
424
use of a real bzrdir.
426
_mod_bzrdir.BzrDir.__init__(self, transport, format)
427
# this object holds a delegated bzrdir that uses file-level operations
428
# to talk to the other side
429
self._real_bzrdir = None
430
self._has_working_tree = None
431
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
432
# create_branch for details.
433
self._next_open_branch_result = None
436
medium = transport.get_smart_medium()
437
self._client = client._SmartClient(medium)
439
self._client = _client
446
return '%s(%r)' % (self.__class__.__name__, self._client)
448
def _probe_bzrdir(self):
449
medium = self._client._medium
450
path = self._path_for_remote_call(self._client)
451
if medium._is_remote_before((2, 1)):
455
self._rpc_open_2_1(path)
457
except errors.UnknownSmartMethod:
458
medium._remember_remote_is_before((2, 1))
461
def _rpc_open_2_1(self, path):
462
response = self._call('BzrDir.open_2.1', path)
463
if response == ('no',):
464
raise errors.NotBranchError(path=self.root_transport.base)
465
elif response[0] == 'yes':
466
if response[1] == 'yes':
467
self._has_working_tree = True
468
elif response[1] == 'no':
469
self._has_working_tree = False
471
raise errors.UnexpectedSmartServerResponse(response)
473
raise errors.UnexpectedSmartServerResponse(response)
475
def _rpc_open(self, path):
476
response = self._call('BzrDir.open', path)
477
if response not in [('yes',), ('no',)]:
478
raise errors.UnexpectedSmartServerResponse(response)
479
if response == ('no',):
480
raise errors.NotBranchError(path=self.root_transport.base)
482
def _ensure_real(self):
483
"""Ensure that there is a _real_bzrdir set.
485
Used before calls to self._real_bzrdir.
487
if not self._real_bzrdir:
488
if 'hpssvfs' in debug.debug_flags:
490
warning('VFS BzrDir access triggered\n%s',
491
''.join(traceback.format_stack()))
492
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
493
self.root_transport, probers=[_mod_bzrdir.BzrProber])
494
self._format._network_name = \
495
self._real_bzrdir._format.network_name()
497
def _translate_error(self, err, **context):
498
_translate_error(err, bzrdir=self, **context)
500
def break_lock(self):
501
# Prevent aliasing problems in the next_open_branch_result cache.
502
# See create_branch for rationale.
503
self._next_open_branch_result = None
504
return _mod_bzrdir.BzrDir.break_lock(self)
506
def _vfs_checkout_metadir(self):
508
return self._real_bzrdir.checkout_metadir()
510
def checkout_metadir(self):
511
"""Retrieve the controldir format to use for checkouts of this one.
513
medium = self._client._medium
514
if medium._is_remote_before((2, 5)):
515
return self._vfs_checkout_metadir()
516
path = self._path_for_remote_call(self._client)
518
response = self._client.call('BzrDir.checkout_metadir',
520
except errors.UnknownSmartMethod:
521
medium._remember_remote_is_before((2, 5))
522
return self._vfs_checkout_metadir()
523
if len(response) != 3:
524
raise errors.UnexpectedSmartServerResponse(response)
525
control_name, repo_name, branch_name = response
527
format = controldir.network_format_registry.get(control_name)
529
raise errors.UnknownFormatError(kind='control',
533
repo_format = _mod_repository.network_format_registry.get(
536
raise errors.UnknownFormatError(kind='repository',
538
format.repository_format = repo_format
541
format.set_branch_format(
542
branch.network_format_registry.get(branch_name))
544
raise errors.UnknownFormatError(kind='branch',
548
def _vfs_cloning_metadir(self, require_stacking=False):
550
return self._real_bzrdir.cloning_metadir(
551
require_stacking=require_stacking)
553
def cloning_metadir(self, require_stacking=False):
554
medium = self._client._medium
555
if medium._is_remote_before((1, 13)):
556
return self._vfs_cloning_metadir(require_stacking=require_stacking)
557
verb = 'BzrDir.cloning_metadir'
562
path = self._path_for_remote_call(self._client)
564
response = self._call(verb, path, stacking)
565
except errors.UnknownSmartMethod:
566
medium._remember_remote_is_before((1, 13))
567
return self._vfs_cloning_metadir(require_stacking=require_stacking)
568
except errors.UnknownErrorFromSmartServer as err:
569
if err.error_tuple != ('BranchReference',):
571
# We need to resolve the branch reference to determine the
572
# cloning_metadir. This causes unnecessary RPCs to open the
573
# referenced branch (and bzrdir, etc) but only when the caller
574
# didn't already resolve the branch reference.
575
referenced_branch = self.open_branch()
576
return referenced_branch.bzrdir.cloning_metadir()
577
if len(response) != 3:
578
raise errors.UnexpectedSmartServerResponse(response)
579
control_name, repo_name, branch_info = response
580
if len(branch_info) != 2:
581
raise errors.UnexpectedSmartServerResponse(response)
582
branch_ref, branch_name = branch_info
584
format = controldir.network_format_registry.get(control_name)
586
raise errors.UnknownFormatError(kind='control', format=control_name)
590
format.repository_format = _mod_repository.network_format_registry.get(
593
raise errors.UnknownFormatError(kind='repository',
595
if branch_ref == 'ref':
596
# XXX: we need possible_transports here to avoid reopening the
597
# connection to the referenced location
598
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
599
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
600
format.set_branch_format(branch_format)
601
elif branch_ref == 'branch':
604
branch_format = branch.network_format_registry.get(
607
raise errors.UnknownFormatError(kind='branch',
609
format.set_branch_format(branch_format)
611
raise errors.UnexpectedSmartServerResponse(response)
614
def create_repository(self, shared=False):
615
# as per meta1 formats - just delegate to the format object which may
617
result = self._format.repository_format.initialize(self, shared)
618
if not isinstance(result, RemoteRepository):
619
return self.open_repository()
623
def destroy_repository(self):
624
"""See BzrDir.destroy_repository"""
625
path = self._path_for_remote_call(self._client)
627
response = self._call('BzrDir.destroy_repository', path)
628
except errors.UnknownSmartMethod:
630
self._real_bzrdir.destroy_repository()
632
if response[0] != 'ok':
633
raise SmartProtocolError('unexpected response code %s' % (response,))
635
def create_branch(self, name=None, repository=None,
636
append_revisions_only=None):
638
name = self._get_selected_branch()
640
raise errors.NoColocatedBranchSupport(self)
641
# as per meta1 formats - just delegate to the format object which may
643
real_branch = self._format.get_branch_format().initialize(self,
644
name=name, repository=repository,
645
append_revisions_only=append_revisions_only)
646
if not isinstance(real_branch, RemoteBranch):
647
if not isinstance(repository, RemoteRepository):
648
raise AssertionError(
649
'need a RemoteRepository to use with RemoteBranch, got %r'
651
result = RemoteBranch(self, repository, real_branch, name=name)
654
# BzrDir.clone_on_transport() uses the result of create_branch but does
655
# not return it to its callers; we save approximately 8% of our round
656
# trips by handing the branch we created back to the first caller to
657
# open_branch rather than probing anew. Long term we need a API in
658
# bzrdir that doesn't discard result objects (like result_branch).
660
self._next_open_branch_result = result
663
def destroy_branch(self, name=None):
664
"""See BzrDir.destroy_branch"""
666
name = self._get_selected_branch()
668
raise errors.NoColocatedBranchSupport(self)
669
path = self._path_for_remote_call(self._client)
675
response = self._call('BzrDir.destroy_branch', path, *args)
676
except errors.UnknownSmartMethod:
678
self._real_bzrdir.destroy_branch(name=name)
679
self._next_open_branch_result = None
681
self._next_open_branch_result = None
682
if response[0] != 'ok':
683
raise SmartProtocolError('unexpected response code %s' % (response,))
685
def create_workingtree(self, revision_id=None, from_branch=None,
686
accelerator_tree=None, hardlink=False):
687
raise errors.NotLocalUrl(self.transport.base)
689
def find_branch_format(self, name=None):
690
"""Find the branch 'format' for this bzrdir.
692
This might be a synthetic object for e.g. RemoteBranch and SVN.
694
b = self.open_branch(name=name)
697
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
698
path = self._path_for_remote_call(self._client)
700
response, handler = self._call_expecting_body(
701
'BzrDir.get_branches', path)
702
except errors.UnknownSmartMethod:
704
return self._real_bzrdir.get_branches()
705
if response[0] != "success":
706
raise errors.UnexpectedSmartServerResponse(response)
707
body = bencode.bdecode(handler.read_body_bytes())
709
for name, value in viewitems(body):
710
ret[name] = self._open_branch(name, value[0], value[1],
711
possible_transports=possible_transports,
712
ignore_fallbacks=ignore_fallbacks)
715
def set_branch_reference(self, target_branch, name=None):
716
"""See BzrDir.set_branch_reference()."""
718
name = self._get_selected_branch()
720
raise errors.NoColocatedBranchSupport(self)
722
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
724
def get_branch_reference(self, name=None):
725
"""See BzrDir.get_branch_reference()."""
727
name = self._get_selected_branch()
729
raise errors.NoColocatedBranchSupport(self)
730
response = self._get_branch_reference()
731
if response[0] == 'ref':
736
def _get_branch_reference(self):
737
path = self._path_for_remote_call(self._client)
738
medium = self._client._medium
740
('BzrDir.open_branchV3', (2, 1)),
741
('BzrDir.open_branchV2', (1, 13)),
742
('BzrDir.open_branch', None),
744
for verb, required_version in candidate_calls:
745
if required_version and medium._is_remote_before(required_version):
748
response = self._call(verb, path)
749
except errors.UnknownSmartMethod:
750
if required_version is None:
752
medium._remember_remote_is_before(required_version)
755
if verb == 'BzrDir.open_branch':
756
if response[0] != 'ok':
757
raise errors.UnexpectedSmartServerResponse(response)
758
if response[1] != '':
759
return ('ref', response[1])
761
return ('branch', '')
762
if response[0] not in ('ref', 'branch'):
763
raise errors.UnexpectedSmartServerResponse(response)
766
def _get_tree_branch(self, name=None):
767
"""See BzrDir._get_tree_branch()."""
768
return None, self.open_branch(name=name)
770
def _open_branch(self, name, kind, location_or_format,
771
ignore_fallbacks=False, possible_transports=None):
773
# a branch reference, use the existing BranchReference logic.
774
format = BranchReferenceFormat()
775
return format.open(self, name=name, _found=True,
776
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
777
possible_transports=possible_transports)
778
branch_format_name = location_or_format
779
if not branch_format_name:
780
branch_format_name = None
781
format = RemoteBranchFormat(network_name=branch_format_name)
782
return RemoteBranch(self, self.find_repository(), format=format,
783
setup_stacking=not ignore_fallbacks, name=name,
784
possible_transports=possible_transports)
786
def open_branch(self, name=None, unsupported=False,
787
ignore_fallbacks=False, possible_transports=None):
789
name = self._get_selected_branch()
791
raise errors.NoColocatedBranchSupport(self)
793
raise NotImplementedError('unsupported flag support not implemented yet.')
794
if self._next_open_branch_result is not None:
795
# See create_branch for details.
796
result = self._next_open_branch_result
797
self._next_open_branch_result = None
799
response = self._get_branch_reference()
800
return self._open_branch(name, response[0], response[1],
801
possible_transports=possible_transports,
802
ignore_fallbacks=ignore_fallbacks)
804
def _open_repo_v1(self, path):
805
verb = 'BzrDir.find_repository'
806
response = self._call(verb, path)
807
if response[0] != 'ok':
808
raise errors.UnexpectedSmartServerResponse(response)
809
# servers that only support the v1 method don't support external
812
repo = self._real_bzrdir.open_repository()
813
response = response + ('no', repo._format.network_name())
814
return response, repo
816
def _open_repo_v2(self, path):
817
verb = 'BzrDir.find_repositoryV2'
818
response = self._call(verb, path)
819
if response[0] != 'ok':
820
raise errors.UnexpectedSmartServerResponse(response)
822
repo = self._real_bzrdir.open_repository()
823
response = response + (repo._format.network_name(),)
824
return response, repo
826
def _open_repo_v3(self, path):
827
verb = 'BzrDir.find_repositoryV3'
828
medium = self._client._medium
829
if medium._is_remote_before((1, 13)):
830
raise errors.UnknownSmartMethod(verb)
832
response = self._call(verb, path)
833
except errors.UnknownSmartMethod:
834
medium._remember_remote_is_before((1, 13))
836
if response[0] != 'ok':
837
raise errors.UnexpectedSmartServerResponse(response)
838
return response, None
840
def open_repository(self):
841
path = self._path_for_remote_call(self._client)
843
for probe in [self._open_repo_v3, self._open_repo_v2,
846
response, real_repo = probe(path)
848
except errors.UnknownSmartMethod:
851
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
852
if response[0] != 'ok':
853
raise errors.UnexpectedSmartServerResponse(response)
854
if len(response) != 6:
855
raise SmartProtocolError('incorrect response length %s' % (response,))
856
if response[1] == '':
857
# repo is at this dir.
858
format = response_tuple_to_repo_format(response[2:])
859
# Used to support creating a real format instance when needed.
860
format._creating_bzrdir = self
861
remote_repo = RemoteRepository(self, format)
862
format._creating_repo = remote_repo
863
if real_repo is not None:
864
remote_repo._set_real_repository(real_repo)
867
raise errors.NoRepositoryPresent(self)
869
def has_workingtree(self):
870
if self._has_working_tree is None:
871
path = self._path_for_remote_call(self._client)
873
response = self._call('BzrDir.has_workingtree', path)
874
except errors.UnknownSmartMethod:
876
self._has_working_tree = self._real_bzrdir.has_workingtree()
878
if response[0] not in ('yes', 'no'):
879
raise SmartProtocolError('unexpected response code %s' % (response,))
880
self._has_working_tree = (response[0] == 'yes')
881
return self._has_working_tree
883
def open_workingtree(self, recommend_upgrade=True):
884
if self.has_workingtree():
885
raise errors.NotLocalUrl(self.root_transport)
887
raise errors.NoWorkingTree(self.root_transport.base)
889
def _path_for_remote_call(self, client):
890
"""Return the path to be used for this bzrdir in a remote call."""
891
return urlutils.split_segment_parameters_raw(
892
client.remote_path_from_transport(self.root_transport))[0]
894
def get_branch_transport(self, branch_format, name=None):
896
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
898
def get_repository_transport(self, repository_format):
900
return self._real_bzrdir.get_repository_transport(repository_format)
902
def get_workingtree_transport(self, workingtree_format):
904
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
906
def can_convert_format(self):
907
"""Upgrading of remote bzrdirs is not supported yet."""
910
def needs_format_conversion(self, format):
911
"""Upgrading of remote bzrdirs is not supported yet."""
914
def _get_config(self):
915
return RemoteBzrDirConfig(self)
917
def _get_config_store(self):
918
return RemoteControlStore(self)
921
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
922
"""Format for repositories accessed over a _SmartClient.
924
Instances of this repository are represented by RemoteRepository
927
The RemoteRepositoryFormat is parameterized during construction
928
to reflect the capabilities of the real, remote format. Specifically
929
the attributes rich_root_data and supports_tree_reference are set
930
on a per instance basis, and are not set (and should not be) at
933
:ivar _custom_format: If set, a specific concrete repository format that
934
will be used when initializing a repository with this
935
RemoteRepositoryFormat.
936
:ivar _creating_repo: If set, the repository object that this
937
RemoteRepositoryFormat was created for: it can be called into
938
to obtain data like the network name.
941
_matchingbzrdir = RemoteBzrDirFormat()
942
supports_full_versioned_files = True
943
supports_leaving_lock = True
946
_mod_repository.RepositoryFormat.__init__(self)
947
self._custom_format = None
948
self._network_name = None
949
self._creating_bzrdir = None
950
self._revision_graph_can_have_wrong_parents = None
951
self._supports_chks = None
952
self._supports_external_lookups = None
953
self._supports_tree_reference = None
954
self._supports_funky_characters = None
955
self._supports_nesting_repositories = None
956
self._rich_root_data = None
959
return "%s(_network_name=%r)" % (self.__class__.__name__,
963
def fast_deltas(self):
965
return self._custom_format.fast_deltas
968
def rich_root_data(self):
969
if self._rich_root_data is None:
971
self._rich_root_data = self._custom_format.rich_root_data
972
return self._rich_root_data
975
def supports_chks(self):
976
if self._supports_chks is None:
978
self._supports_chks = self._custom_format.supports_chks
979
return self._supports_chks
982
def supports_external_lookups(self):
983
if self._supports_external_lookups is None:
985
self._supports_external_lookups = \
986
self._custom_format.supports_external_lookups
987
return self._supports_external_lookups
990
def supports_funky_characters(self):
991
if self._supports_funky_characters is None:
993
self._supports_funky_characters = \
994
self._custom_format.supports_funky_characters
995
return self._supports_funky_characters
998
def supports_nesting_repositories(self):
999
if self._supports_nesting_repositories is None:
1001
self._supports_nesting_repositories = \
1002
self._custom_format.supports_nesting_repositories
1003
return self._supports_nesting_repositories
1006
def supports_tree_reference(self):
1007
if self._supports_tree_reference is None:
1009
self._supports_tree_reference = \
1010
self._custom_format.supports_tree_reference
1011
return self._supports_tree_reference
1014
def revision_graph_can_have_wrong_parents(self):
1015
if self._revision_graph_can_have_wrong_parents is None:
1017
self._revision_graph_can_have_wrong_parents = \
1018
self._custom_format.revision_graph_can_have_wrong_parents
1019
return self._revision_graph_can_have_wrong_parents
1021
def _vfs_initialize(self, a_bzrdir, shared):
1022
"""Helper for common code in initialize."""
1023
if self._custom_format:
1024
# Custom format requested
1025
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1026
elif self._creating_bzrdir is not None:
1027
# Use the format that the repository we were created to back
1029
prior_repo = self._creating_bzrdir.open_repository()
1030
prior_repo._ensure_real()
1031
result = prior_repo._real_repository._format.initialize(
1032
a_bzrdir, shared=shared)
1034
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1035
# support remote initialization.
1036
# We delegate to a real object at this point (as RemoteBzrDir
1037
# delegate to the repository format which would lead to infinite
1038
# recursion if we just called a_bzrdir.create_repository.
1039
a_bzrdir._ensure_real()
1040
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1041
if not isinstance(result, RemoteRepository):
1042
return self.open(a_bzrdir)
1046
def initialize(self, a_bzrdir, shared=False):
1047
# Being asked to create on a non RemoteBzrDir:
1048
if not isinstance(a_bzrdir, RemoteBzrDir):
1049
return self._vfs_initialize(a_bzrdir, shared)
1050
medium = a_bzrdir._client._medium
1051
if medium._is_remote_before((1, 13)):
1052
return self._vfs_initialize(a_bzrdir, shared)
1053
# Creating on a remote bzr dir.
1054
# 1) get the network name to use.
1055
if self._custom_format:
1056
network_name = self._custom_format.network_name()
1057
elif self._network_name:
1058
network_name = self._network_name
1060
# Select the current breezy default and ask for that.
1061
reference_bzrdir_format = controldir.format_registry.get('default')()
1062
reference_format = reference_bzrdir_format.repository_format
1063
network_name = reference_format.network_name()
1064
# 2) try direct creation via RPC
1065
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1066
verb = 'BzrDir.create_repository'
1070
shared_str = 'False'
1072
response = a_bzrdir._call(verb, path, network_name, shared_str)
1073
except errors.UnknownSmartMethod:
1074
# Fallback - use vfs methods
1075
medium._remember_remote_is_before((1, 13))
1076
return self._vfs_initialize(a_bzrdir, shared)
1078
# Turn the response into a RemoteRepository object.
1079
format = response_tuple_to_repo_format(response[1:])
1080
# Used to support creating a real format instance when needed.
1081
format._creating_bzrdir = a_bzrdir
1082
remote_repo = RemoteRepository(a_bzrdir, format)
1083
format._creating_repo = remote_repo
1086
def open(self, a_bzrdir):
1087
if not isinstance(a_bzrdir, RemoteBzrDir):
1088
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1089
return a_bzrdir.open_repository()
1091
def _ensure_real(self):
1092
if self._custom_format is None:
1094
self._custom_format = _mod_repository.network_format_registry.get(
1097
raise errors.UnknownFormatError(kind='repository',
1098
format=self._network_name)
1101
def _fetch_order(self):
1103
return self._custom_format._fetch_order
1106
def _fetch_uses_deltas(self):
1108
return self._custom_format._fetch_uses_deltas
1111
def _fetch_reconcile(self):
1113
return self._custom_format._fetch_reconcile
1115
def get_format_description(self):
1117
return 'Remote: ' + self._custom_format.get_format_description()
1119
def __eq__(self, other):
1120
return self.__class__ is other.__class__
1122
def network_name(self):
1123
if self._network_name:
1124
return self._network_name
1125
self._creating_repo._ensure_real()
1126
return self._creating_repo._real_repository._format.network_name()
1129
def pack_compresses(self):
1131
return self._custom_format.pack_compresses
1134
def _serializer(self):
1136
return self._custom_format._serializer
1139
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1140
lock._RelockDebugMixin):
1141
"""Repository accessed over rpc.
1143
For the moment most operations are performed using local transport-backed
1147
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1148
"""Create a RemoteRepository instance.
1150
:param remote_bzrdir: The bzrdir hosting this repository.
1151
:param format: The RemoteFormat object to use.
1152
:param real_repository: If not None, a local implementation of the
1153
repository logic for the repository, usually accessing the data
1155
:param _client: Private testing parameter - override the smart client
1156
to be used by the repository.
1159
self._real_repository = real_repository
1161
self._real_repository = None
1162
self.bzrdir = remote_bzrdir
1164
self._client = remote_bzrdir._client
1166
self._client = _client
1167
self._format = format
1168
self._lock_mode = None
1169
self._lock_token = None
1170
self._write_group_tokens = None
1171
self._lock_count = 0
1172
self._leave_lock = False
1173
# Cache of revision parents; misses are cached during read locks, and
1174
# write locks when no _real_repository has been set.
1175
self._unstacked_provider = graph.CachingParentsProvider(
1176
get_parent_map=self._get_parent_map_rpc)
1177
self._unstacked_provider.disable_cache()
1179
# These depend on the actual remote format, so force them off for
1180
# maximum compatibility. XXX: In future these should depend on the
1181
# remote repository instance, but this is irrelevant until we perform
1182
# reconcile via an RPC call.
1183
self._reconcile_does_inventory_gc = False
1184
self._reconcile_fixes_text_parents = False
1185
self._reconcile_backsup_inventory = False
1186
self.base = self.bzrdir.transport.base
1187
# Additional places to query for data.
1188
self._fallback_repositories = []
1191
def user_transport(self):
1192
return self.bzrdir.user_transport
1195
def control_transport(self):
1196
# XXX: Normally you shouldn't directly get at the remote repository
1197
# transport, but I'm not sure it's worth making this method
1198
# optional -- mbp 2010-04-21
1199
return self.bzrdir.get_repository_transport(None)
1202
return "%s(%s)" % (self.__class__.__name__, self.base)
1206
def abort_write_group(self, suppress_errors=False):
1207
"""Complete a write group on the decorated repository.
1209
Smart methods perform operations in a single step so this API
1210
is not really applicable except as a compatibility thunk
1211
for older plugins that don't use e.g. the CommitBuilder
1214
:param suppress_errors: see Repository.abort_write_group.
1216
if self._real_repository:
1218
return self._real_repository.abort_write_group(
1219
suppress_errors=suppress_errors)
1220
if not self.is_in_write_group():
1222
mutter('(suppressed) not in write group')
1224
raise errors.BzrError("not in write group")
1225
path = self.bzrdir._path_for_remote_call(self._client)
1227
response = self._call('Repository.abort_write_group', path,
1228
self._lock_token, self._write_group_tokens)
1229
except Exception as exc:
1230
self._write_group = None
1231
if not suppress_errors:
1233
mutter('abort_write_group failed')
1234
log_exception_quietly()
1235
note(gettext('bzr: ERROR (ignored): %s'), exc)
1237
if response != ('ok', ):
1238
raise errors.UnexpectedSmartServerResponse(response)
1239
self._write_group_tokens = None
1242
def chk_bytes(self):
1243
"""Decorate the real repository for now.
1245
In the long term a full blown network facility is needed to avoid
1246
creating a real repository object locally.
1249
return self._real_repository.chk_bytes
1251
def commit_write_group(self):
1252
"""Complete a write group on the decorated repository.
1254
Smart methods perform operations in a single step so this API
1255
is not really applicable except as a compatibility thunk
1256
for older plugins that don't use e.g. the CommitBuilder
1259
if self._real_repository:
1261
return self._real_repository.commit_write_group()
1262
if not self.is_in_write_group():
1263
raise errors.BzrError("not in write group")
1264
path = self.bzrdir._path_for_remote_call(self._client)
1265
response = self._call('Repository.commit_write_group', path,
1266
self._lock_token, self._write_group_tokens)
1267
if response != ('ok', ):
1268
raise errors.UnexpectedSmartServerResponse(response)
1269
self._write_group_tokens = None
1270
# Refresh data after writing to the repository.
1273
def resume_write_group(self, tokens):
1274
if self._real_repository:
1275
return self._real_repository.resume_write_group(tokens)
1276
path = self.bzrdir._path_for_remote_call(self._client)
1278
response = self._call('Repository.check_write_group', path,
1279
self._lock_token, tokens)
1280
except errors.UnknownSmartMethod:
1282
return self._real_repository.resume_write_group(tokens)
1283
if response != ('ok', ):
1284
raise errors.UnexpectedSmartServerResponse(response)
1285
self._write_group_tokens = tokens
1287
def suspend_write_group(self):
1288
if self._real_repository:
1289
return self._real_repository.suspend_write_group()
1290
ret = self._write_group_tokens or []
1291
self._write_group_tokens = None
1294
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1296
return self._real_repository.get_missing_parent_inventories(
1297
check_for_missing_texts=check_for_missing_texts)
1299
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1301
return self._real_repository.get_rev_id_for_revno(
1304
def get_rev_id_for_revno(self, revno, known_pair):
1305
"""See Repository.get_rev_id_for_revno."""
1306
path = self.bzrdir._path_for_remote_call(self._client)
1308
if self._client._medium._is_remote_before((1, 17)):
1309
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1310
response = self._call(
1311
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1312
except errors.UnknownSmartMethod:
1313
self._client._medium._remember_remote_is_before((1, 17))
1314
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1315
if response[0] == 'ok':
1316
return True, response[1]
1317
elif response[0] == 'history-incomplete':
1318
known_pair = response[1:3]
1319
for fallback in self._fallback_repositories:
1320
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1325
# Not found in any fallbacks
1326
return False, known_pair
1328
raise errors.UnexpectedSmartServerResponse(response)
1330
def _ensure_real(self):
1331
"""Ensure that there is a _real_repository set.
1333
Used before calls to self._real_repository.
1335
Note that _ensure_real causes many roundtrips to the server which are
1336
not desirable, and prevents the use of smart one-roundtrip RPC's to
1337
perform complex operations (such as accessing parent data, streaming
1338
revisions etc). Adding calls to _ensure_real should only be done when
1339
bringing up new functionality, adding fallbacks for smart methods that
1340
require a fallback path, and never to replace an existing smart method
1341
invocation. If in doubt chat to the bzr network team.
1343
if self._real_repository is None:
1344
if 'hpssvfs' in debug.debug_flags:
1346
warning('VFS Repository access triggered\n%s',
1347
''.join(traceback.format_stack()))
1348
self._unstacked_provider.missing_keys.clear()
1349
self.bzrdir._ensure_real()
1350
self._set_real_repository(
1351
self.bzrdir._real_bzrdir.open_repository())
1353
def _translate_error(self, err, **context):
1354
self.bzrdir._translate_error(err, repository=self, **context)
1356
def find_text_key_references(self):
1357
"""Find the text key references within the repository.
1359
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1360
to whether they were referred to by the inventory of the
1361
revision_id that they contain. The inventory texts from all present
1362
revision ids are assessed to generate this report.
1365
return self._real_repository.find_text_key_references()
1367
def _generate_text_key_index(self):
1368
"""Generate a new text key index for the repository.
1370
This is an expensive function that will take considerable time to run.
1372
:return: A dict mapping (file_id, revision_id) tuples to a list of
1373
parents, also (file_id, revision_id) tuples.
1376
return self._real_repository._generate_text_key_index()
1378
def _get_revision_graph(self, revision_id):
1379
"""Private method for using with old (< 1.2) servers to fallback."""
1380
if revision_id is None:
1382
elif _mod_revision.is_null(revision_id):
1385
path = self.bzrdir._path_for_remote_call(self._client)
1386
response = self._call_expecting_body(
1387
'Repository.get_revision_graph', path, revision_id)
1388
response_tuple, response_handler = response
1389
if response_tuple[0] != 'ok':
1390
raise errors.UnexpectedSmartServerResponse(response_tuple)
1391
coded = response_handler.read_body_bytes()
1393
# no revisions in this repository!
1395
lines = coded.split('\n')
1398
d = tuple(line.split())
1399
revision_graph[d[0]] = d[1:]
1401
return revision_graph
1403
def _get_sink(self):
1404
"""See Repository._get_sink()."""
1405
return RemoteStreamSink(self)
1407
def _get_source(self, to_format):
1408
"""Return a source for streaming from this repository."""
1409
return RemoteStreamSource(self, to_format)
1412
def get_file_graph(self):
1413
return graph.Graph(self.texts)
1416
def has_revision(self, revision_id):
1417
"""True if this repository has a copy of the revision."""
1418
# Copy of breezy.repository.Repository.has_revision
1419
return revision_id in self.has_revisions((revision_id,))
1422
def has_revisions(self, revision_ids):
1423
"""Probe to find out the presence of multiple revisions.
1425
:param revision_ids: An iterable of revision_ids.
1426
:return: A set of the revision_ids that were present.
1428
# Copy of breezy.repository.Repository.has_revisions
1429
parent_map = self.get_parent_map(revision_ids)
1430
result = set(parent_map)
1431
if _mod_revision.NULL_REVISION in revision_ids:
1432
result.add(_mod_revision.NULL_REVISION)
1435
def _has_same_fallbacks(self, other_repo):
1436
"""Returns true if the repositories have the same fallbacks."""
1437
# XXX: copied from Repository; it should be unified into a base class
1438
# <https://bugs.launchpad.net/bzr/+bug/401622>
1439
my_fb = self._fallback_repositories
1440
other_fb = other_repo._fallback_repositories
1441
if len(my_fb) != len(other_fb):
1443
for f, g in zip(my_fb, other_fb):
1444
if not f.has_same_location(g):
1448
def has_same_location(self, other):
1449
# TODO: Move to RepositoryBase and unify with the regular Repository
1450
# one; unfortunately the tests rely on slightly different behaviour at
1451
# present -- mbp 20090710
1452
return (self.__class__ is other.__class__ and
1453
self.bzrdir.transport.base == other.bzrdir.transport.base)
1455
def get_graph(self, other_repository=None):
1456
"""Return the graph for this repository format"""
1457
parents_provider = self._make_parents_provider(other_repository)
1458
return graph.Graph(parents_provider)
1461
def get_known_graph_ancestry(self, revision_ids):
1462
"""Return the known graph for a set of revision ids and their ancestors.
1464
st = static_tuple.StaticTuple
1465
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1466
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1467
return graph.GraphThunkIdsToKeys(known_graph)
1469
def gather_stats(self, revid=None, committers=None):
1470
"""See Repository.gather_stats()."""
1471
path = self.bzrdir._path_for_remote_call(self._client)
1472
# revid can be None to indicate no revisions, not just NULL_REVISION
1473
if revid is None or _mod_revision.is_null(revid):
1477
if committers is None or not committers:
1478
fmt_committers = 'no'
1480
fmt_committers = 'yes'
1481
response_tuple, response_handler = self._call_expecting_body(
1482
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1483
if response_tuple[0] != 'ok':
1484
raise errors.UnexpectedSmartServerResponse(response_tuple)
1486
body = response_handler.read_body_bytes()
1488
for line in body.split('\n'):
1491
key, val_text = line.split(':')
1492
if key in ('revisions', 'size', 'committers'):
1493
result[key] = int(val_text)
1494
elif key in ('firstrev', 'latestrev'):
1495
values = val_text.split(' ')[1:]
1496
result[key] = (float(values[0]), int(values[1]))
1500
def find_branches(self, using=False):
1501
"""See Repository.find_branches()."""
1502
# should be an API call to the server.
1504
return self._real_repository.find_branches(using=using)
1506
def get_physical_lock_status(self):
1507
"""See Repository.get_physical_lock_status()."""
1508
path = self.bzrdir._path_for_remote_call(self._client)
1510
response = self._call('Repository.get_physical_lock_status', path)
1511
except errors.UnknownSmartMethod:
1513
return self._real_repository.get_physical_lock_status()
1514
if response[0] not in ('yes', 'no'):
1515
raise errors.UnexpectedSmartServerResponse(response)
1516
return (response[0] == 'yes')
1518
def is_in_write_group(self):
1519
"""Return True if there is an open write group.
1521
write groups are only applicable locally for the smart server..
1523
if self._write_group_tokens is not None:
1525
if self._real_repository:
1526
return self._real_repository.is_in_write_group()
1528
def is_locked(self):
1529
return self._lock_count >= 1
1531
def is_shared(self):
1532
"""See Repository.is_shared()."""
1533
path = self.bzrdir._path_for_remote_call(self._client)
1534
response = self._call('Repository.is_shared', path)
1535
if response[0] not in ('yes', 'no'):
1536
raise SmartProtocolError('unexpected response code %s' % (response,))
1537
return response[0] == 'yes'
1539
def is_write_locked(self):
1540
return self._lock_mode == 'w'
1542
def _warn_if_deprecated(self, branch=None):
1543
# If we have a real repository, the check will be done there, if we
1544
# don't the check will be done remotely.
1547
def lock_read(self):
1548
"""Lock the repository for read operations.
1550
:return: A breezy.lock.LogicalLockResult.
1552
# wrong eventually - want a local lock cache context
1553
if not self._lock_mode:
1554
self._note_lock('r')
1555
self._lock_mode = 'r'
1556
self._lock_count = 1
1557
self._unstacked_provider.enable_cache(cache_misses=True)
1558
if self._real_repository is not None:
1559
self._real_repository.lock_read()
1560
for repo in self._fallback_repositories:
1563
self._lock_count += 1
1564
return lock.LogicalLockResult(self.unlock)
1566
def _remote_lock_write(self, token):
1567
path = self.bzrdir._path_for_remote_call(self._client)
1570
err_context = {'token': token}
1571
response = self._call('Repository.lock_write', path, token,
1573
if response[0] == 'ok':
1574
ok, token = response
1577
raise errors.UnexpectedSmartServerResponse(response)
1579
def lock_write(self, token=None, _skip_rpc=False):
1580
if not self._lock_mode:
1581
self._note_lock('w')
1583
if self._lock_token is not None:
1584
if token != self._lock_token:
1585
raise errors.TokenMismatch(token, self._lock_token)
1586
self._lock_token = token
1588
self._lock_token = self._remote_lock_write(token)
1589
# if self._lock_token is None, then this is something like packs or
1590
# svn where we don't get to lock the repo, or a weave style repository
1591
# where we cannot lock it over the wire and attempts to do so will
1593
if self._real_repository is not None:
1594
self._real_repository.lock_write(token=self._lock_token)
1595
if token is not None:
1596
self._leave_lock = True
1598
self._leave_lock = False
1599
self._lock_mode = 'w'
1600
self._lock_count = 1
1601
cache_misses = self._real_repository is None
1602
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1603
for repo in self._fallback_repositories:
1604
# Writes don't affect fallback repos
1606
elif self._lock_mode == 'r':
1607
raise errors.ReadOnlyError(self)
1609
self._lock_count += 1
1610
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1612
def leave_lock_in_place(self):
1613
if not self._lock_token:
1614
raise NotImplementedError(self.leave_lock_in_place)
1615
self._leave_lock = True
1617
def dont_leave_lock_in_place(self):
1618
if not self._lock_token:
1619
raise NotImplementedError(self.dont_leave_lock_in_place)
1620
self._leave_lock = False
1622
def _set_real_repository(self, repository):
1623
"""Set the _real_repository for this repository.
1625
:param repository: The repository to fallback to for non-hpss
1626
implemented operations.
1628
if self._real_repository is not None:
1629
# Replacing an already set real repository.
1630
# We cannot do this [currently] if the repository is locked -
1631
# synchronised state might be lost.
1632
if self.is_locked():
1633
raise AssertionError('_real_repository is already set')
1634
if isinstance(repository, RemoteRepository):
1635
raise AssertionError()
1636
self._real_repository = repository
1637
# three code paths happen here:
1638
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1639
# up stacking. In this case self._fallback_repositories is [], and the
1640
# real repo is already setup. Preserve the real repo and
1641
# RemoteRepository.add_fallback_repository will avoid adding
1643
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1644
# ensure_real is triggered from a branch, the real repository to
1645
# set already has a matching list with separate instances, but
1646
# as they are also RemoteRepositories we don't worry about making the
1647
# lists be identical.
1648
# 3) new servers, RemoteRepository.ensure_real is triggered before
1649
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1650
# and need to populate it.
1651
if (self._fallback_repositories and
1652
len(self._real_repository._fallback_repositories) !=
1653
len(self._fallback_repositories)):
1654
if len(self._real_repository._fallback_repositories):
1655
raise AssertionError(
1656
"cannot cleanly remove existing _fallback_repositories")
1657
for fb in self._fallback_repositories:
1658
self._real_repository.add_fallback_repository(fb)
1659
if self._lock_mode == 'w':
1660
# if we are already locked, the real repository must be able to
1661
# acquire the lock with our token.
1662
self._real_repository.lock_write(self._lock_token)
1663
elif self._lock_mode == 'r':
1664
self._real_repository.lock_read()
1665
if self._write_group_tokens is not None:
1666
# if we are already in a write group, resume it
1667
self._real_repository.resume_write_group(self._write_group_tokens)
1668
self._write_group_tokens = None
1670
def start_write_group(self):
1671
"""Start a write group on the decorated repository.
1673
Smart methods perform operations in a single step so this API
1674
is not really applicable except as a compatibility thunk
1675
for older plugins that don't use e.g. the CommitBuilder
1678
if self._real_repository:
1680
return self._real_repository.start_write_group()
1681
if not self.is_write_locked():
1682
raise errors.NotWriteLocked(self)
1683
if self._write_group_tokens is not None:
1684
raise errors.BzrError('already in a write group')
1685
path = self.bzrdir._path_for_remote_call(self._client)
1687
response = self._call('Repository.start_write_group', path,
1689
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1691
return self._real_repository.start_write_group()
1692
if response[0] != 'ok':
1693
raise errors.UnexpectedSmartServerResponse(response)
1694
self._write_group_tokens = response[1]
1696
def _unlock(self, token):
1697
path = self.bzrdir._path_for_remote_call(self._client)
1699
# with no token the remote repository is not persistently locked.
1701
err_context = {'token': token}
1702
response = self._call('Repository.unlock', path, token,
1704
if response == ('ok',):
1707
raise errors.UnexpectedSmartServerResponse(response)
1709
@only_raises(errors.LockNotHeld, errors.LockBroken)
1711
if not self._lock_count:
1712
return lock.cant_unlock_not_held(self)
1713
self._lock_count -= 1
1714
if self._lock_count > 0:
1716
self._unstacked_provider.disable_cache()
1717
old_mode = self._lock_mode
1718
self._lock_mode = None
1720
# The real repository is responsible at present for raising an
1721
# exception if it's in an unfinished write group. However, it
1722
# normally will *not* actually remove the lock from disk - that's
1723
# done by the server on receiving the Repository.unlock call.
1724
# This is just to let the _real_repository stay up to date.
1725
if self._real_repository is not None:
1726
self._real_repository.unlock()
1727
elif self._write_group_tokens is not None:
1728
self.abort_write_group()
1730
# The rpc-level lock should be released even if there was a
1731
# problem releasing the vfs-based lock.
1733
# Only write-locked repositories need to make a remote method
1734
# call to perform the unlock.
1735
old_token = self._lock_token
1736
self._lock_token = None
1737
if not self._leave_lock:
1738
self._unlock(old_token)
1739
# Fallbacks are always 'lock_read()' so we don't pay attention to
1741
for repo in self._fallback_repositories:
1744
def break_lock(self):
1745
# should hand off to the network
1746
path = self.bzrdir._path_for_remote_call(self._client)
1748
response = self._call("Repository.break_lock", path)
1749
except errors.UnknownSmartMethod:
1751
return self._real_repository.break_lock()
1752
if response != ('ok',):
1753
raise errors.UnexpectedSmartServerResponse(response)
1755
def _get_tarball(self, compression):
1756
"""Return a TemporaryFile containing a repository tarball.
1758
Returns None if the server does not support sending tarballs.
1761
path = self.bzrdir._path_for_remote_call(self._client)
1763
response, protocol = self._call_expecting_body(
1764
'Repository.tarball', path, compression)
1765
except errors.UnknownSmartMethod:
1766
protocol.cancel_read_body()
1768
if response[0] == 'ok':
1769
# Extract the tarball and return it
1770
t = tempfile.NamedTemporaryFile()
1771
# TODO: rpc layer should read directly into it...
1772
t.write(protocol.read_body_bytes())
1775
raise errors.UnexpectedSmartServerResponse(response)
1778
def sprout(self, to_bzrdir, revision_id=None):
1779
"""Create a descendent repository for new development.
1781
Unlike clone, this does not copy the settings of the repository.
1783
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1784
dest_repo.fetch(self, revision_id=revision_id)
1787
def _create_sprouting_repo(self, a_bzrdir, shared):
1788
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1789
# use target default format.
1790
dest_repo = a_bzrdir.create_repository()
1792
# Most control formats need the repository to be specifically
1793
# created, but on some old all-in-one formats it's not needed
1795
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1796
except errors.UninitializableFormat:
1797
dest_repo = a_bzrdir.open_repository()
1800
### These methods are just thin shims to the VFS object for now.
1803
def revision_tree(self, revision_id):
1804
revision_id = _mod_revision.ensure_null(revision_id)
1805
if revision_id == _mod_revision.NULL_REVISION:
1806
return InventoryRevisionTree(self,
1807
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1809
return list(self.revision_trees([revision_id]))[0]
1811
def get_serializer_format(self):
1812
path = self.bzrdir._path_for_remote_call(self._client)
1814
response = self._call('VersionedFileRepository.get_serializer_format',
1816
except errors.UnknownSmartMethod:
1818
return self._real_repository.get_serializer_format()
1819
if response[0] != 'ok':
1820
raise errors.UnexpectedSmartServerResponse(response)
1823
def get_commit_builder(self, branch, parents, config, timestamp=None,
1824
timezone=None, committer=None, revprops=None,
1825
revision_id=None, lossy=False):
1826
"""Obtain a CommitBuilder for this repository.
1828
:param branch: Branch to commit to.
1829
:param parents: Revision ids of the parents of the new revision.
1830
:param config: Configuration to use.
1831
:param timestamp: Optional timestamp recorded for commit.
1832
:param timezone: Optional timezone for timestamp.
1833
:param committer: Optional committer to set for commit.
1834
:param revprops: Optional dictionary of revision properties.
1835
:param revision_id: Optional revision id.
1836
:param lossy: Whether to discard data that can not be natively
1837
represented, when pushing to a foreign VCS
1839
if self._fallback_repositories and not self._format.supports_chks:
1840
raise errors.BzrError("Cannot commit directly to a stacked branch"
1841
" in pre-2a formats. See "
1842
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1843
if self._format.rich_root_data:
1844
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1846
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1847
result = commit_builder_kls(self, parents, config,
1848
timestamp, timezone, committer, revprops, revision_id,
1850
self.start_write_group()
1853
def add_fallback_repository(self, repository):
1854
"""Add a repository to use for looking up data not held locally.
1856
:param repository: A repository.
1858
if not self._format.supports_external_lookups:
1859
raise errors.UnstackableRepositoryFormat(
1860
self._format.network_name(), self.base)
1861
# We need to accumulate additional repositories here, to pass them in
1864
# Make the check before we lock: this raises an exception.
1865
self._check_fallback_repository(repository)
1866
if self.is_locked():
1867
# We will call fallback.unlock() when we transition to the unlocked
1868
# state, so always add a lock here. If a caller passes us a locked
1869
# repository, they are responsible for unlocking it later.
1870
repository.lock_read()
1871
self._fallback_repositories.append(repository)
1872
# If self._real_repository was parameterised already (e.g. because a
1873
# _real_branch had its get_stacked_on_url method called), then the
1874
# repository to be added may already be in the _real_repositories list.
1875
if self._real_repository is not None:
1876
fallback_locations = [repo.user_url for repo in
1877
self._real_repository._fallback_repositories]
1878
if repository.user_url not in fallback_locations:
1879
self._real_repository.add_fallback_repository(repository)
1881
def _check_fallback_repository(self, repository):
1882
"""Check that this repository can fallback to repository safely.
1884
Raise an error if not.
1886
:param repository: A repository to fallback to.
1888
return _mod_repository.InterRepository._assert_same_model(
1891
def add_inventory(self, revid, inv, parents):
1893
return self._real_repository.add_inventory(revid, inv, parents)
1895
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1896
parents, basis_inv=None, propagate_caches=False):
1898
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1899
delta, new_revision_id, parents, basis_inv=basis_inv,
1900
propagate_caches=propagate_caches)
1902
def add_revision(self, revision_id, rev, inv=None):
1903
_mod_revision.check_not_reserved_id(revision_id)
1904
key = (revision_id,)
1905
# check inventory present
1906
if not self.inventories.get_parent_map([key]):
1908
raise errors.WeaveRevisionNotPresent(revision_id,
1911
# yes, this is not suitable for adding with ghosts.
1912
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1915
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1916
self._add_revision(rev)
1918
def _add_revision(self, rev):
1919
if self._real_repository is not None:
1920
return self._real_repository._add_revision(rev)
1921
text = self._serializer.write_revision_to_string(rev)
1922
key = (rev.revision_id,)
1923
parents = tuple((parent,) for parent in rev.parent_ids)
1924
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1925
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1926
self._format, self._write_group_tokens)
1929
def get_inventory(self, revision_id):
1930
return list(self.iter_inventories([revision_id]))[0]
1932
def _iter_inventories_rpc(self, revision_ids, ordering):
1933
if ordering is None:
1934
ordering = 'unordered'
1935
path = self.bzrdir._path_for_remote_call(self._client)
1936
body = "\n".join(revision_ids)
1937
response_tuple, response_handler = (
1938
self._call_with_body_bytes_expecting_body(
1939
"VersionedFileRepository.get_inventories",
1940
(path, ordering), body))
1941
if response_tuple[0] != "ok":
1942
raise errors.UnexpectedSmartServerResponse(response_tuple)
1943
deserializer = inventory_delta.InventoryDeltaDeserializer()
1944
byte_stream = response_handler.read_streamed_body()
1945
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1947
# no results whatsoever
1949
src_format, stream = decoded
1950
if src_format.network_name() != self._format.network_name():
1951
raise AssertionError(
1952
"Mismatched RemoteRepository and stream src %r, %r" % (
1953
src_format.network_name(), self._format.network_name()))
1954
# ignore the src format, it's not really relevant
1955
prev_inv = Inventory(root_id=None,
1956
revision_id=_mod_revision.NULL_REVISION)
1957
# there should be just one substream, with inventory deltas
1958
substream_kind, substream = next(stream)
1959
if substream_kind != "inventory-deltas":
1960
raise AssertionError(
1961
"Unexpected stream %r received" % substream_kind)
1962
for record in substream:
1963
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1964
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1965
if parent_id != prev_inv.revision_id:
1966
raise AssertionError("invalid base %r != %r" % (parent_id,
1967
prev_inv.revision_id))
1968
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1969
yield inv, inv.revision_id
1972
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1974
return self._real_repository._iter_inventories(revision_ids, ordering)
1976
def iter_inventories(self, revision_ids, ordering=None):
1977
"""Get many inventories by revision_ids.
1979
This will buffer some or all of the texts used in constructing the
1980
inventories in memory, but will only parse a single inventory at a
1983
:param revision_ids: The expected revision ids of the inventories.
1984
:param ordering: optional ordering, e.g. 'topological'. If not
1985
specified, the order of revision_ids will be preserved (by
1986
buffering if necessary).
1987
:return: An iterator of inventories.
1989
if ((None in revision_ids)
1990
or (_mod_revision.NULL_REVISION in revision_ids)):
1991
raise ValueError('cannot get null revision inventory')
1992
for inv, revid in self._iter_inventories(revision_ids, ordering):
1994
raise errors.NoSuchRevision(self, revid)
1997
def _iter_inventories(self, revision_ids, ordering=None):
1998
if len(revision_ids) == 0:
2000
missing = set(revision_ids)
2001
if ordering is None:
2002
order_as_requested = True
2004
order = list(revision_ids)
2006
next_revid = order.pop()
2008
order_as_requested = False
2009
if ordering != 'unordered' and self._fallback_repositories:
2010
raise ValueError('unsupported ordering %r' % ordering)
2011
iter_inv_fns = [self._iter_inventories_rpc] + [
2012
fallback._iter_inventories for fallback in
2013
self._fallback_repositories]
2015
for iter_inv in iter_inv_fns:
2016
request = [revid for revid in revision_ids if revid in missing]
2017
for inv, revid in iter_inv(request, ordering):
2020
missing.remove(inv.revision_id)
2021
if ordering != 'unordered':
2025
if order_as_requested:
2026
# Yield as many results as we can while preserving order.
2027
while next_revid in invs:
2028
inv = invs.pop(next_revid)
2029
yield inv, inv.revision_id
2031
next_revid = order.pop()
2033
# We still want to fully consume the stream, just
2034
# in case it is not actually finished at this point
2037
except errors.UnknownSmartMethod:
2038
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2042
if order_as_requested:
2043
if next_revid is not None:
2044
yield None, next_revid
2047
yield invs.get(revid), revid
2050
yield None, missing.pop()
2053
def get_revision(self, revision_id):
2054
return self.get_revisions([revision_id])[0]
2056
def get_transaction(self):
2058
return self._real_repository.get_transaction()
2061
def clone(self, a_bzrdir, revision_id=None):
2062
dest_repo = self._create_sprouting_repo(
2063
a_bzrdir, shared=self.is_shared())
2064
self.copy_content_into(dest_repo, revision_id)
2067
def make_working_trees(self):
2068
"""See Repository.make_working_trees"""
2069
path = self.bzrdir._path_for_remote_call(self._client)
2071
response = self._call('Repository.make_working_trees', path)
2072
except errors.UnknownSmartMethod:
2074
return self._real_repository.make_working_trees()
2075
if response[0] not in ('yes', 'no'):
2076
raise SmartProtocolError('unexpected response code %s' % (response,))
2077
return response[0] == 'yes'
2079
def refresh_data(self):
2080
"""Re-read any data needed to synchronise with disk.
2082
This method is intended to be called after another repository instance
2083
(such as one used by a smart server) has inserted data into the
2084
repository. On all repositories this will work outside of write groups.
2085
Some repository formats (pack and newer for breezy native formats)
2086
support refresh_data inside write groups. If called inside a write
2087
group on a repository that does not support refreshing in a write group
2088
IsInWriteGroupError will be raised.
2090
if self._real_repository is not None:
2091
self._real_repository.refresh_data()
2092
# Refresh the parents cache for this object
2093
self._unstacked_provider.disable_cache()
2094
self._unstacked_provider.enable_cache()
2096
def revision_ids_to_search_result(self, result_set):
2097
"""Convert a set of revision ids to a graph SearchResult."""
2098
result_parents = set()
2099
for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
2100
result_parents.update(parents)
2101
included_keys = result_set.intersection(result_parents)
2102
start_keys = result_set.difference(included_keys)
2103
exclude_keys = result_parents.difference(result_set)
2104
result = vf_search.SearchResult(start_keys, exclude_keys,
2105
len(result_set), result_set)
2109
def search_missing_revision_ids(self, other,
2110
find_ghosts=True, revision_ids=None, if_present_ids=None,
2112
"""Return the revision ids that other has that this does not.
2114
These are returned in topological order.
2116
revision_id: only return revision ids included by revision_id.
2118
inter_repo = _mod_repository.InterRepository.get(other, self)
2119
return inter_repo.search_missing_revision_ids(
2120
find_ghosts=find_ghosts, revision_ids=revision_ids,
2121
if_present_ids=if_present_ids, limit=limit)
2123
def fetch(self, source, revision_id=None, find_ghosts=False,
2125
# No base implementation to use as RemoteRepository is not a subclass
2126
# of Repository; so this is a copy of Repository.fetch().
2127
if fetch_spec is not None and revision_id is not None:
2128
raise AssertionError(
2129
"fetch_spec and revision_id are mutually exclusive.")
2130
if self.is_in_write_group():
2131
raise errors.InternalBzrError(
2132
"May not fetch while in a write group.")
2133
# fast path same-url fetch operations
2134
if (self.has_same_location(source)
2135
and fetch_spec is None
2136
and self._has_same_fallbacks(source)):
2137
# check that last_revision is in 'from' and then return a
2139
if (revision_id is not None and
2140
not _mod_revision.is_null(revision_id)):
2141
self.get_revision(revision_id)
2143
# if there is no specific appropriate InterRepository, this will get
2144
# the InterRepository base class, which raises an
2145
# IncompatibleRepositories when asked to fetch.
2146
inter = _mod_repository.InterRepository.get(source, self)
2147
if (fetch_spec is not None and
2148
not getattr(inter, "supports_fetch_spec", False)):
2149
raise errors.UnsupportedOperation(
2150
"fetch_spec not supported for %r" % inter)
2151
return inter.fetch(revision_id=revision_id,
2152
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2154
def create_bundle(self, target, base, fileobj, format=None):
2156
self._real_repository.create_bundle(target, base, fileobj, format)
2158
def fileids_altered_by_revision_ids(self, revision_ids):
2160
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2162
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2164
return self._real_repository._get_versioned_file_checker(
2165
revisions, revision_versions_cache)
2167
def _iter_files_bytes_rpc(self, desired_files, absent):
2168
path = self.bzrdir._path_for_remote_call(self._client)
2171
for (file_id, revid, identifier) in desired_files:
2172
lines.append("%s\0%s" % (
2173
osutils.safe_file_id(file_id),
2174
osutils.safe_revision_id(revid)))
2175
identifiers.append(identifier)
2176
(response_tuple, response_handler) = (
2177
self._call_with_body_bytes_expecting_body(
2178
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2179
if response_tuple != ('ok', ):
2180
response_handler.cancel_read_body()
2181
raise errors.UnexpectedSmartServerResponse(response_tuple)
2182
byte_stream = response_handler.read_streamed_body()
2183
def decompress_stream(start, byte_stream, unused):
2184
decompressor = zlib.decompressobj()
2185
yield decompressor.decompress(start)
2186
while decompressor.unused_data == "":
2188
data = next(byte_stream)
2189
except StopIteration:
2191
yield decompressor.decompress(data)
2192
yield decompressor.flush()
2193
unused.append(decompressor.unused_data)
2196
while not "\n" in unused:
2197
unused += next(byte_stream)
2198
header, rest = unused.split("\n", 1)
2199
args = header.split("\0")
2200
if args[0] == "absent":
2201
absent[identifiers[int(args[3])]] = (args[1], args[2])
2204
elif args[0] == "ok":
2207
raise errors.UnexpectedSmartServerResponse(args)
2209
yield (identifiers[idx],
2210
decompress_stream(rest, byte_stream, unused_chunks))
2211
unused = "".join(unused_chunks)
2213
def iter_files_bytes(self, desired_files):
2214
"""See Repository.iter_file_bytes.
2218
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2219
desired_files, absent):
2220
yield identifier, bytes_iterator
2221
for fallback in self._fallback_repositories:
2224
desired_files = [(key[0], key[1], identifier)
2225
for identifier, key in viewitems(absent)]
2226
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2227
del absent[identifier]
2228
yield identifier, bytes_iterator
2230
# There may be more missing items, but raise an exception
2232
missing_identifier = next(iter(absent))
2233
missing_key = absent[missing_identifier]
2234
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2235
file_id=missing_key[0])
2236
except errors.UnknownSmartMethod:
2238
for (identifier, bytes_iterator) in (
2239
self._real_repository.iter_files_bytes(desired_files)):
2240
yield identifier, bytes_iterator
2242
def get_cached_parent_map(self, revision_ids):
2243
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2244
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2246
def get_parent_map(self, revision_ids):
2247
"""See breezy.Graph.get_parent_map()."""
2248
return self._make_parents_provider().get_parent_map(revision_ids)
2250
def _get_parent_map_rpc(self, keys):
2251
"""Helper for get_parent_map that performs the RPC."""
2252
medium = self._client._medium
2253
if medium._is_remote_before((1, 2)):
2254
# We already found out that the server can't understand
2255
# Repository.get_parent_map requests, so just fetch the whole
2258
# Note that this reads the whole graph, when only some keys are
2259
# wanted. On this old server there's no way (?) to get them all
2260
# in one go, and the user probably will have seen a warning about
2261
# the server being old anyhow.
2262
rg = self._get_revision_graph(None)
2263
# There is an API discrepancy between get_parent_map and
2264
# get_revision_graph. Specifically, a "key:()" pair in
2265
# get_revision_graph just means a node has no parents. For
2266
# "get_parent_map" it means the node is a ghost. So fix up the
2267
# graph to correct this.
2268
# https://bugs.launchpad.net/bzr/+bug/214894
2269
# There is one other "bug" which is that ghosts in
2270
# get_revision_graph() are not returned at all. But we won't worry
2271
# about that for now.
2272
for node_id, parent_ids in viewitems(rg):
2273
if parent_ids == ():
2274
rg[node_id] = (NULL_REVISION,)
2275
rg[NULL_REVISION] = ()
2280
raise ValueError('get_parent_map(None) is not valid')
2281
if NULL_REVISION in keys:
2282
keys.discard(NULL_REVISION)
2283
found_parents = {NULL_REVISION:()}
2285
return found_parents
2288
# TODO(Needs analysis): We could assume that the keys being requested
2289
# from get_parent_map are in a breadth first search, so typically they
2290
# will all be depth N from some common parent, and we don't have to
2291
# have the server iterate from the root parent, but rather from the
2292
# keys we're searching; and just tell the server the keyspace we
2293
# already have; but this may be more traffic again.
2295
# Transform self._parents_map into a search request recipe.
2296
# TODO: Manage this incrementally to avoid covering the same path
2297
# repeatedly. (The server will have to on each request, but the less
2298
# work done the better).
2300
# Negative caching notes:
2301
# new server sends missing when a request including the revid
2302
# 'include-missing:' is present in the request.
2303
# missing keys are serialised as missing:X, and we then call
2304
# provider.note_missing(X) for-all X
2305
parents_map = self._unstacked_provider.get_cached_map()
2306
if parents_map is None:
2307
# Repository is not locked, so there's no cache.
2309
if _DEFAULT_SEARCH_DEPTH <= 0:
2310
(start_set, stop_keys,
2311
key_count) = vf_search.search_result_from_parent_map(
2312
parents_map, self._unstacked_provider.missing_keys)
2314
(start_set, stop_keys,
2315
key_count) = vf_search.limited_search_result_from_parent_map(
2316
parents_map, self._unstacked_provider.missing_keys,
2317
keys, depth=_DEFAULT_SEARCH_DEPTH)
2318
recipe = ('manual', start_set, stop_keys, key_count)
2319
body = self._serialise_search_recipe(recipe)
2320
path = self.bzrdir._path_for_remote_call(self._client)
2322
if not isinstance(key, str):
2324
"key %r not a plain string" % (key,))
2325
verb = 'Repository.get_parent_map'
2326
args = (path, 'include-missing:') + tuple(keys)
2328
response = self._call_with_body_bytes_expecting_body(
2330
except errors.UnknownSmartMethod:
2331
# Server does not support this method, so get the whole graph.
2332
# Worse, we have to force a disconnection, because the server now
2333
# doesn't realise it has a body on the wire to consume, so the
2334
# only way to recover is to abandon the connection.
2336
'Server is too old for fast get_parent_map, reconnecting. '
2337
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2339
# To avoid having to disconnect repeatedly, we keep track of the
2340
# fact the server doesn't understand remote methods added in 1.2.
2341
medium._remember_remote_is_before((1, 2))
2342
# Recurse just once and we should use the fallback code.
2343
return self._get_parent_map_rpc(keys)
2344
response_tuple, response_handler = response
2345
if response_tuple[0] not in ['ok']:
2346
response_handler.cancel_read_body()
2347
raise errors.UnexpectedSmartServerResponse(response_tuple)
2348
if response_tuple[0] == 'ok':
2349
coded = bz2.decompress(response_handler.read_body_bytes())
2351
# no revisions found
2353
lines = coded.split('\n')
2356
d = tuple(line.split())
2358
revision_graph[d[0]] = d[1:]
2361
if d[0].startswith('missing:'):
2363
self._unstacked_provider.note_missing_key(revid)
2365
# no parents - so give the Graph result
2367
revision_graph[d[0]] = (NULL_REVISION,)
2368
return revision_graph
2371
def get_signature_text(self, revision_id):
2372
path = self.bzrdir._path_for_remote_call(self._client)
2374
response_tuple, response_handler = self._call_expecting_body(
2375
'Repository.get_revision_signature_text', path, revision_id)
2376
except errors.UnknownSmartMethod:
2378
return self._real_repository.get_signature_text(revision_id)
2379
except errors.NoSuchRevision as err:
2380
for fallback in self._fallback_repositories:
2382
return fallback.get_signature_text(revision_id)
2383
except errors.NoSuchRevision:
2387
if response_tuple[0] != 'ok':
2388
raise errors.UnexpectedSmartServerResponse(response_tuple)
2389
return response_handler.read_body_bytes()
2392
def _get_inventory_xml(self, revision_id):
2393
# This call is used by older working tree formats,
2394
# which stored a serialized basis inventory.
2396
return self._real_repository._get_inventory_xml(revision_id)
2399
def reconcile(self, other=None, thorough=False):
2400
from .reconcile import RepoReconciler
2401
path = self.bzrdir._path_for_remote_call(self._client)
2403
response, handler = self._call_expecting_body(
2404
'Repository.reconcile', path, self._lock_token)
2405
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2407
return self._real_repository.reconcile(other=other, thorough=thorough)
2408
if response != ('ok', ):
2409
raise errors.UnexpectedSmartServerResponse(response)
2410
body = handler.read_body_bytes()
2411
result = RepoReconciler(self)
2412
for line in body.split('\n'):
2415
key, val_text = line.split(':')
2416
if key == "garbage_inventories":
2417
result.garbage_inventories = int(val_text)
2418
elif key == "inconsistent_parents":
2419
result.inconsistent_parents = int(val_text)
2421
mutter("unknown reconcile key %r" % key)
2424
def all_revision_ids(self):
2425
path = self.bzrdir._path_for_remote_call(self._client)
2427
response_tuple, response_handler = self._call_expecting_body(
2428
"Repository.all_revision_ids", path)
2429
except errors.UnknownSmartMethod:
2431
return self._real_repository.all_revision_ids()
2432
if response_tuple != ("ok", ):
2433
raise errors.UnexpectedSmartServerResponse(response_tuple)
2434
revids = set(response_handler.read_body_bytes().splitlines())
2435
for fallback in self._fallback_repositories:
2436
revids.update(set(fallback.all_revision_ids()))
2439
def _filtered_revision_trees(self, revision_ids, file_ids):
2440
"""Return Tree for a revision on this branch with only some files.
2442
:param revision_ids: a sequence of revision-ids;
2443
a revision-id may not be None or 'null:'
2444
:param file_ids: if not None, the result is filtered
2445
so that only those file-ids, their parents and their
2446
children are included.
2448
inventories = self.iter_inventories(revision_ids)
2449
for inv in inventories:
2450
# Should we introduce a FilteredRevisionTree class rather
2451
# than pre-filter the inventory here?
2452
filtered_inv = inv.filter(file_ids)
2453
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2456
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2457
medium = self._client._medium
2458
if medium._is_remote_before((1, 2)):
2460
for delta in self._real_repository.get_deltas_for_revisions(
2461
revisions, specific_fileids):
2464
# Get the revision-ids of interest
2465
required_trees = set()
2466
for revision in revisions:
2467
required_trees.add(revision.revision_id)
2468
required_trees.update(revision.parent_ids[:1])
2470
# Get the matching filtered trees. Note that it's more
2471
# efficient to pass filtered trees to changes_from() rather
2472
# than doing the filtering afterwards. changes_from() could
2473
# arguably do the filtering itself but it's path-based, not
2474
# file-id based, so filtering before or afterwards is
2476
if specific_fileids is None:
2477
trees = dict((t.get_revision_id(), t) for
2478
t in self.revision_trees(required_trees))
2480
trees = dict((t.get_revision_id(), t) for
2481
t in self._filtered_revision_trees(required_trees,
2484
# Calculate the deltas
2485
for revision in revisions:
2486
if not revision.parent_ids:
2487
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2489
old_tree = trees[revision.parent_ids[0]]
2490
yield trees[revision.revision_id].changes_from(old_tree)
2493
def get_revision_delta(self, revision_id, specific_fileids=None):
2494
r = self.get_revision(revision_id)
2495
return list(self.get_deltas_for_revisions([r],
2496
specific_fileids=specific_fileids))[0]
2499
def revision_trees(self, revision_ids):
2500
inventories = self.iter_inventories(revision_ids)
2501
for inv in inventories:
2502
yield InventoryRevisionTree(self, inv, inv.revision_id)
2505
def get_revision_reconcile(self, revision_id):
2507
return self._real_repository.get_revision_reconcile(revision_id)
2510
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2512
return self._real_repository.check(revision_ids=revision_ids,
2513
callback_refs=callback_refs, check_repo=check_repo)
2515
def copy_content_into(self, destination, revision_id=None):
2516
"""Make a complete copy of the content in self into destination.
2518
This is a destructive operation! Do not use it on existing
2521
interrepo = _mod_repository.InterRepository.get(self, destination)
2522
return interrepo.copy_content(revision_id)
2524
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2525
# get a tarball of the remote repository, and copy from that into the
2528
# TODO: Maybe a progress bar while streaming the tarball?
2529
note(gettext("Copying repository content as tarball..."))
2530
tar_file = self._get_tarball('bz2')
2531
if tar_file is None:
2533
destination = to_bzrdir.create_repository()
2535
tar = tarfile.open('repository', fileobj=tar_file,
2537
tmpdir = osutils.mkdtemp()
2539
_extract_tar(tar, tmpdir)
2540
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2541
tmp_repo = tmp_bzrdir.open_repository()
2542
tmp_repo.copy_content_into(destination, revision_id)
2544
osutils.rmtree(tmpdir)
2548
# TODO: Suggestion from john: using external tar is much faster than
2549
# python's tarfile library, but it may not work on windows.
2552
def inventories(self):
2553
"""Decorate the real repository for now.
2555
In the long term a full blown network facility is needed to
2556
avoid creating a real repository object locally.
2559
return self._real_repository.inventories
2562
def pack(self, hint=None, clean_obsolete_packs=False):
2563
"""Compress the data within the repository.
2568
body = "".join([l+"\n" for l in hint])
2569
path = self.bzrdir._path_for_remote_call(self._client)
2571
response, handler = self._call_with_body_bytes_expecting_body(
2572
'Repository.pack', (path, self._lock_token,
2573
str(clean_obsolete_packs)), body)
2574
except errors.UnknownSmartMethod:
2576
return self._real_repository.pack(hint=hint,
2577
clean_obsolete_packs=clean_obsolete_packs)
2578
handler.cancel_read_body()
2579
if response != ('ok', ):
2580
raise errors.UnexpectedSmartServerResponse(response)
2583
def revisions(self):
2584
"""Decorate the real repository for now.
2586
In the long term a full blown network facility is needed.
2589
return self._real_repository.revisions
2591
def set_make_working_trees(self, new_value):
2593
new_value_str = "True"
2595
new_value_str = "False"
2596
path = self.bzrdir._path_for_remote_call(self._client)
2598
response = self._call(
2599
'Repository.set_make_working_trees', path, new_value_str)
2600
except errors.UnknownSmartMethod:
2602
self._real_repository.set_make_working_trees(new_value)
2604
if response[0] != 'ok':
2605
raise errors.UnexpectedSmartServerResponse(response)
2608
def signatures(self):
2609
"""Decorate the real repository for now.
2611
In the long term a full blown network facility is needed to avoid
2612
creating a real repository object locally.
2615
return self._real_repository.signatures
2618
def sign_revision(self, revision_id, gpg_strategy):
2619
testament = _mod_testament.Testament.from_revision(self, revision_id)
2620
plaintext = testament.as_short_text()
2621
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2625
"""Decorate the real repository for now.
2627
In the long term a full blown network facility is needed to avoid
2628
creating a real repository object locally.
2631
return self._real_repository.texts
2633
def _iter_revisions_rpc(self, revision_ids):
2634
body = "\n".join(revision_ids)
2635
path = self.bzrdir._path_for_remote_call(self._client)
2636
response_tuple, response_handler = (
2637
self._call_with_body_bytes_expecting_body(
2638
"Repository.iter_revisions", (path, ), body))
2639
if response_tuple[0] != "ok":
2640
raise errors.UnexpectedSmartServerResponse(response_tuple)
2641
serializer_format = response_tuple[1]
2642
serializer = serializer_format_registry.get(serializer_format)
2643
byte_stream = response_handler.read_streamed_body()
2644
decompressor = zlib.decompressobj()
2646
for bytes in byte_stream:
2647
chunks.append(decompressor.decompress(bytes))
2648
if decompressor.unused_data != "":
2649
chunks.append(decompressor.flush())
2650
yield serializer.read_revision_from_string("".join(chunks))
2651
unused = decompressor.unused_data
2652
decompressor = zlib.decompressobj()
2653
chunks = [decompressor.decompress(unused)]
2654
chunks.append(decompressor.flush())
2655
text = "".join(chunks)
2657
yield serializer.read_revision_from_string("".join(chunks))
2660
def get_revisions(self, revision_ids):
2661
if revision_ids is None:
2662
revision_ids = self.all_revision_ids()
2664
for rev_id in revision_ids:
2665
if not rev_id or not isinstance(rev_id, basestring):
2666
raise errors.InvalidRevisionId(
2667
revision_id=rev_id, branch=self)
2669
missing = set(revision_ids)
2671
for rev in self._iter_revisions_rpc(revision_ids):
2672
missing.remove(rev.revision_id)
2673
revs[rev.revision_id] = rev
2674
except errors.UnknownSmartMethod:
2676
return self._real_repository.get_revisions(revision_ids)
2677
for fallback in self._fallback_repositories:
2680
for revid in list(missing):
2681
# XXX JRV 2011-11-20: It would be nice if there was a
2682
# public method on Repository that could be used to query
2683
# for revision objects *without* failing completely if one
2684
# was missing. There is VersionedFileRepository._iter_revisions,
2685
# but unfortunately that's private and not provided by
2686
# all repository implementations.
2688
revs[revid] = fallback.get_revision(revid)
2689
except errors.NoSuchRevision:
2692
missing.remove(revid)
2694
raise errors.NoSuchRevision(self, list(missing)[0])
2695
return [revs[revid] for revid in revision_ids]
2697
def supports_rich_root(self):
2698
return self._format.rich_root_data
2701
def _serializer(self):
2702
return self._format._serializer
2705
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2706
signature = gpg_strategy.sign(plaintext)
2707
self.add_signature_text(revision_id, signature)
2709
def add_signature_text(self, revision_id, signature):
2710
if self._real_repository:
2711
# If there is a real repository the write group will
2712
# be in the real repository as well, so use that:
2714
return self._real_repository.add_signature_text(
2715
revision_id, signature)
2716
path = self.bzrdir._path_for_remote_call(self._client)
2717
response, handler = self._call_with_body_bytes_expecting_body(
2718
'Repository.add_signature_text', (path, self._lock_token,
2719
revision_id) + tuple(self._write_group_tokens), signature)
2720
handler.cancel_read_body()
2722
if response[0] != 'ok':
2723
raise errors.UnexpectedSmartServerResponse(response)
2724
self._write_group_tokens = response[1:]
2726
def has_signature_for_revision_id(self, revision_id):
2727
path = self.bzrdir._path_for_remote_call(self._client)
2729
response = self._call('Repository.has_signature_for_revision_id',
2731
except errors.UnknownSmartMethod:
2733
return self._real_repository.has_signature_for_revision_id(
2735
if response[0] not in ('yes', 'no'):
2736
raise SmartProtocolError('unexpected response code %s' % (response,))
2737
if response[0] == 'yes':
2739
for fallback in self._fallback_repositories:
2740
if fallback.has_signature_for_revision_id(revision_id):
2745
def verify_revision_signature(self, revision_id, gpg_strategy):
2746
if not self.has_signature_for_revision_id(revision_id):
2747
return gpg.SIGNATURE_NOT_SIGNED, None
2748
signature = self.get_signature_text(revision_id)
2750
testament = _mod_testament.Testament.from_revision(self, revision_id)
2751
plaintext = testament.as_short_text()
2753
return gpg_strategy.verify(signature, plaintext)
2755
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2757
return self._real_repository.item_keys_introduced_by(revision_ids,
2758
_files_pb=_files_pb)
2760
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2762
return self._real_repository._find_inconsistent_revision_parents(
2765
def _check_for_inconsistent_revision_parents(self):
2767
return self._real_repository._check_for_inconsistent_revision_parents()
2769
def _make_parents_provider(self, other=None):
2770
providers = [self._unstacked_provider]
2771
if other is not None:
2772
providers.insert(0, other)
2773
return graph.StackedParentsProvider(_LazyListJoin(
2774
providers, self._fallback_repositories))
2776
def _serialise_search_recipe(self, recipe):
2777
"""Serialise a graph search recipe.
2779
:param recipe: A search recipe (start, stop, count).
2780
:return: Serialised bytes.
2782
start_keys = ' '.join(recipe[1])
2783
stop_keys = ' '.join(recipe[2])
2784
count = str(recipe[3])
2785
return '\n'.join((start_keys, stop_keys, count))
2787
def _serialise_search_result(self, search_result):
2788
parts = search_result.get_network_struct()
2789
return '\n'.join(parts)
2792
path = self.bzrdir._path_for_remote_call(self._client)
2794
response = self._call('PackRepository.autopack', path)
2795
except errors.UnknownSmartMethod:
2797
self._real_repository._pack_collection.autopack()
2800
if response[0] != 'ok':
2801
raise errors.UnexpectedSmartServerResponse(response)
2804
class RemoteStreamSink(vf_repository.StreamSink):
2806
def _insert_real(self, stream, src_format, resume_tokens):
2807
self.target_repo._ensure_real()
2808
sink = self.target_repo._real_repository._get_sink()
2809
result = sink.insert_stream(stream, src_format, resume_tokens)
2811
self.target_repo.autopack()
2814
def insert_stream(self, stream, src_format, resume_tokens):
2815
target = self.target_repo
2816
target._unstacked_provider.missing_keys.clear()
2817
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2818
if target._lock_token:
2819
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2820
lock_args = (target._lock_token or '',)
2822
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2824
client = target._client
2825
medium = client._medium
2826
path = target.bzrdir._path_for_remote_call(client)
2827
# Probe for the verb to use with an empty stream before sending the
2828
# real stream to it. We do this both to avoid the risk of sending a
2829
# large request that is then rejected, and because we don't want to
2830
# implement a way to buffer, rewind, or restart the stream.
2832
for verb, required_version in candidate_calls:
2833
if medium._is_remote_before(required_version):
2836
# We've already done the probing (and set _is_remote_before) on
2837
# a previous insert.
2840
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2842
response = client.call_with_body_stream(
2843
(verb, path, '') + lock_args, byte_stream)
2844
except errors.UnknownSmartMethod:
2845
medium._remember_remote_is_before(required_version)
2851
return self._insert_real(stream, src_format, resume_tokens)
2852
self._last_inv_record = None
2853
self._last_substream = None
2854
if required_version < (1, 19):
2855
# Remote side doesn't support inventory deltas. Wrap the stream to
2856
# make sure we don't send any. If the stream contains inventory
2857
# deltas we'll interrupt the smart insert_stream request and
2859
stream = self._stop_stream_if_inventory_delta(stream)
2860
byte_stream = smart_repo._stream_to_byte_stream(
2862
resume_tokens = ' '.join(resume_tokens)
2863
response = client.call_with_body_stream(
2864
(verb, path, resume_tokens) + lock_args, byte_stream)
2865
if response[0][0] not in ('ok', 'missing-basis'):
2866
raise errors.UnexpectedSmartServerResponse(response)
2867
if self._last_substream is not None:
2868
# The stream included an inventory-delta record, but the remote
2869
# side isn't new enough to support them. So we need to send the
2870
# rest of the stream via VFS.
2871
self.target_repo.refresh_data()
2872
return self._resume_stream_with_vfs(response, src_format)
2873
if response[0][0] == 'missing-basis':
2874
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2875
resume_tokens = tokens
2876
return resume_tokens, set(missing_keys)
2878
self.target_repo.refresh_data()
2881
def _resume_stream_with_vfs(self, response, src_format):
2882
"""Resume sending a stream via VFS, first resending the record and
2883
substream that couldn't be sent via an insert_stream verb.
2885
if response[0][0] == 'missing-basis':
2886
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2887
# Ignore missing_keys, we haven't finished inserting yet
2890
def resume_substream():
2891
# Yield the substream that was interrupted.
2892
for record in self._last_substream:
2894
self._last_substream = None
2895
def resume_stream():
2896
# Finish sending the interrupted substream
2897
yield ('inventory-deltas', resume_substream())
2898
# Then simply continue sending the rest of the stream.
2899
for substream_kind, substream in self._last_stream:
2900
yield substream_kind, substream
2901
return self._insert_real(resume_stream(), src_format, tokens)
2903
def _stop_stream_if_inventory_delta(self, stream):
2904
"""Normally this just lets the original stream pass-through unchanged.
2906
However if any 'inventory-deltas' substream occurs it will stop
2907
streaming, and store the interrupted substream and stream in
2908
self._last_substream and self._last_stream so that the stream can be
2909
resumed by _resume_stream_with_vfs.
2912
stream_iter = iter(stream)
2913
for substream_kind, substream in stream_iter:
2914
if substream_kind == 'inventory-deltas':
2915
self._last_substream = substream
2916
self._last_stream = stream_iter
2919
yield substream_kind, substream
2922
class RemoteStreamSource(vf_repository.StreamSource):
2923
"""Stream data from a remote server."""
2925
def get_stream(self, search):
2926
if (self.from_repository._fallback_repositories and
2927
self.to_format._fetch_order == 'topological'):
2928
return self._real_stream(self.from_repository, search)
2931
repos = [self.from_repository]
2937
repos.extend(repo._fallback_repositories)
2938
sources.append(repo)
2939
return self.missing_parents_chain(search, sources)
2941
def get_stream_for_missing_keys(self, missing_keys):
2942
self.from_repository._ensure_real()
2943
real_repo = self.from_repository._real_repository
2944
real_source = real_repo._get_source(self.to_format)
2945
return real_source.get_stream_for_missing_keys(missing_keys)
2947
def _real_stream(self, repo, search):
2948
"""Get a stream for search from repo.
2950
This never called RemoteStreamSource.get_stream, and is a helper
2951
for RemoteStreamSource._get_stream to allow getting a stream
2952
reliably whether fallback back because of old servers or trying
2953
to stream from a non-RemoteRepository (which the stacked support
2956
source = repo._get_source(self.to_format)
2957
if isinstance(source, RemoteStreamSource):
2959
source = repo._real_repository._get_source(self.to_format)
2960
return source.get_stream(search)
2962
def _get_stream(self, repo, search):
2963
"""Core worker to get a stream from repo for search.
2965
This is used by both get_stream and the stacking support logic. It
2966
deliberately gets a stream for repo which does not need to be
2967
self.from_repository. In the event that repo is not Remote, or
2968
cannot do a smart stream, a fallback is made to the generic
2969
repository._get_stream() interface, via self._real_stream.
2971
In the event of stacking, streams from _get_stream will not
2972
contain all the data for search - this is normal (see get_stream).
2974
:param repo: A repository.
2975
:param search: A search.
2977
# Fallbacks may be non-smart
2978
if not isinstance(repo, RemoteRepository):
2979
return self._real_stream(repo, search)
2980
client = repo._client
2981
medium = client._medium
2982
path = repo.bzrdir._path_for_remote_call(client)
2983
search_bytes = repo._serialise_search_result(search)
2984
args = (path, self.to_format.network_name())
2986
('Repository.get_stream_1.19', (1, 19)),
2987
('Repository.get_stream', (1, 13))]
2990
for verb, version in candidate_verbs:
2991
if medium._is_remote_before(version):
2994
response = repo._call_with_body_bytes_expecting_body(
2995
verb, args, search_bytes)
2996
except errors.UnknownSmartMethod:
2997
medium._remember_remote_is_before(version)
2998
except errors.UnknownErrorFromSmartServer as e:
2999
if isinstance(search, vf_search.EverythingResult):
3000
error_verb = e.error_from_smart_server.error_verb
3001
if error_verb == 'BadSearch':
3002
# Pre-2.4 servers don't support this sort of search.
3003
# XXX: perhaps falling back to VFS on BadSearch is a
3004
# good idea in general? It might provide a little bit
3005
# of protection against client-side bugs.
3006
medium._remember_remote_is_before((2, 4))
3010
response_tuple, response_handler = response
3014
return self._real_stream(repo, search)
3015
if response_tuple[0] != 'ok':
3016
raise errors.UnexpectedSmartServerResponse(response_tuple)
3017
byte_stream = response_handler.read_streamed_body()
3018
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3019
self._record_counter)
3020
if src_format.network_name() != repo._format.network_name():
3021
raise AssertionError(
3022
"Mismatched RemoteRepository and stream src %r, %r" % (
3023
src_format.network_name(), repo._format.network_name()))
3026
def missing_parents_chain(self, search, sources):
3027
"""Chain multiple streams together to handle stacking.
3029
:param search: The overall search to satisfy with streams.
3030
:param sources: A list of Repository objects to query.
3032
self.from_serialiser = self.from_repository._format._serializer
3033
self.seen_revs = set()
3034
self.referenced_revs = set()
3035
# If there are heads in the search, or the key count is > 0, we are not
3037
while not search.is_empty() and len(sources) > 1:
3038
source = sources.pop(0)
3039
stream = self._get_stream(source, search)
3040
for kind, substream in stream:
3041
if kind != 'revisions':
3042
yield kind, substream
3044
yield kind, self.missing_parents_rev_handler(substream)
3045
search = search.refine(self.seen_revs, self.referenced_revs)
3046
self.seen_revs = set()
3047
self.referenced_revs = set()
3048
if not search.is_empty():
3049
for kind, stream in self._get_stream(sources[0], search):
3052
def missing_parents_rev_handler(self, substream):
3053
for content in substream:
3054
revision_bytes = content.get_bytes_as('fulltext')
3055
revision = self.from_serialiser.read_revision_from_string(
3057
self.seen_revs.add(content.key[-1])
3058
self.referenced_revs.update(revision.parent_ids)
3062
class RemoteBranchLockableFiles(LockableFiles):
3063
"""A 'LockableFiles' implementation that talks to a smart server.
3065
This is not a public interface class.
3068
def __init__(self, bzrdir, _client):
3069
self.bzrdir = bzrdir
3070
self._client = _client
3071
self._need_find_modes = True
3072
LockableFiles.__init__(
3073
self, bzrdir.get_branch_transport(None),
3074
'lock', lockdir.LockDir)
3076
def _find_modes(self):
3077
# RemoteBranches don't let the client set the mode of control files.
3078
self._dir_mode = None
3079
self._file_mode = None
3082
class RemoteBranchFormat(branch.BranchFormat):
3084
def __init__(self, network_name=None):
3085
super(RemoteBranchFormat, self).__init__()
3086
self._matchingbzrdir = RemoteBzrDirFormat()
3087
self._matchingbzrdir.set_branch_format(self)
3088
self._custom_format = None
3089
self._network_name = network_name
3091
def __eq__(self, other):
3092
return (isinstance(other, RemoteBranchFormat) and
3093
self.__dict__ == other.__dict__)
3095
def _ensure_real(self):
3096
if self._custom_format is None:
3098
self._custom_format = branch.network_format_registry.get(
3101
raise errors.UnknownFormatError(kind='branch',
3102
format=self._network_name)
3104
def get_format_description(self):
3106
return 'Remote: ' + self._custom_format.get_format_description()
3108
def network_name(self):
3109
return self._network_name
3111
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3112
return a_bzrdir.open_branch(name=name,
3113
ignore_fallbacks=ignore_fallbacks)
3115
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only,
3117
# Initialisation when using a local bzrdir object, or a non-vfs init
3118
# method is not available on the server.
3119
# self._custom_format is always set - the start of initialize ensures
3121
if isinstance(a_bzrdir, RemoteBzrDir):
3122
a_bzrdir._ensure_real()
3123
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3124
name=name, append_revisions_only=append_revisions_only,
3125
repository=repository)
3127
# We assume the bzrdir is parameterised; it may not be.
3128
result = self._custom_format.initialize(a_bzrdir, name=name,
3129
append_revisions_only=append_revisions_only,
3130
repository=repository)
3131
if (isinstance(a_bzrdir, RemoteBzrDir) and
3132
not isinstance(result, RemoteBranch)):
3133
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3137
def initialize(self, a_bzrdir, name=None, repository=None,
3138
append_revisions_only=None):
3140
name = a_bzrdir._get_selected_branch()
3141
# 1) get the network name to use.
3142
if self._custom_format:
3143
network_name = self._custom_format.network_name()
3145
# Select the current breezy default and ask for that.
3146
reference_bzrdir_format = controldir.format_registry.get('default')()
3147
reference_format = reference_bzrdir_format.get_branch_format()
3148
self._custom_format = reference_format
3149
network_name = reference_format.network_name()
3150
# Being asked to create on a non RemoteBzrDir:
3151
if not isinstance(a_bzrdir, RemoteBzrDir):
3152
return self._vfs_initialize(a_bzrdir, name=name,
3153
append_revisions_only=append_revisions_only,
3154
repository=repository)
3155
medium = a_bzrdir._client._medium
3156
if medium._is_remote_before((1, 13)):
3157
return self._vfs_initialize(a_bzrdir, name=name,
3158
append_revisions_only=append_revisions_only,
3159
repository=repository)
3160
# Creating on a remote bzr dir.
3161
# 2) try direct creation via RPC
3162
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3164
# XXX JRV20100304: Support creating colocated branches
3165
raise errors.NoColocatedBranchSupport(self)
3166
verb = 'BzrDir.create_branch'
3168
response = a_bzrdir._call(verb, path, network_name)
3169
except errors.UnknownSmartMethod:
3170
# Fallback - use vfs methods
3171
medium._remember_remote_is_before((1, 13))
3172
return self._vfs_initialize(a_bzrdir, name=name,
3173
append_revisions_only=append_revisions_only,
3174
repository=repository)
3175
if response[0] != 'ok':
3176
raise errors.UnexpectedSmartServerResponse(response)
3177
# Turn the response into a RemoteRepository object.
3178
format = RemoteBranchFormat(network_name=response[1])
3179
repo_format = response_tuple_to_repo_format(response[3:])
3180
repo_path = response[2]
3181
if repository is not None:
3182
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3183
url_diff = urlutils.relative_url(repository.user_url,
3186
raise AssertionError(
3187
'repository.user_url %r does not match URL from server '
3188
'response (%r + %r)'
3189
% (repository.user_url, a_bzrdir.user_url, repo_path))
3190
remote_repo = repository
3193
repo_bzrdir = a_bzrdir
3195
repo_bzrdir = RemoteBzrDir(
3196
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3198
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3199
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3200
format=format, setup_stacking=False, name=name)
3201
if append_revisions_only:
3202
remote_branch.set_append_revisions_only(append_revisions_only)
3203
# XXX: We know this is a new branch, so it must have revno 0, revid
3204
# NULL_REVISION. Creating the branch locked would make this be unable
3205
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3206
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3207
return remote_branch
3209
def make_tags(self, branch):
3211
return self._custom_format.make_tags(branch)
3213
def supports_tags(self):
3214
# Remote branches might support tags, but we won't know until we
3215
# access the real remote branch.
3217
return self._custom_format.supports_tags()
3219
def supports_stacking(self):
3221
return self._custom_format.supports_stacking()
3223
def supports_set_append_revisions_only(self):
3225
return self._custom_format.supports_set_append_revisions_only()
3227
def _use_default_local_heads_to_fetch(self):
3228
# If the branch format is a metadir format *and* its heads_to_fetch
3229
# implementation is not overridden vs the base class, we can use the
3230
# base class logic rather than use the heads_to_fetch RPC. This is
3231
# usually cheaper in terms of net round trips, as the last-revision and
3232
# tags info fetched is cached and would be fetched anyway.
3234
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3235
branch_class = self._custom_format._branch_class()
3236
heads_to_fetch_impl = branch_class.heads_to_fetch.__func__
3237
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.__func__:
3242
class RemoteBranchStore(_mod_config.IniFileStore):
3243
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3245
Note that this is specific to bzr-based formats.
3248
def __init__(self, branch):
3249
super(RemoteBranchStore, self).__init__()
3250
self.branch = branch
3252
self._real_store = None
3254
def external_url(self):
3255
return urlutils.join(self.branch.user_url, 'branch.conf')
3257
def _load_content(self):
3258
path = self.branch._remote_path()
3260
response, handler = self.branch._call_expecting_body(
3261
'Branch.get_config_file', path)
3262
except errors.UnknownSmartMethod:
3264
return self._real_store._load_content()
3265
if len(response) and response[0] != 'ok':
3266
raise errors.UnexpectedSmartServerResponse(response)
3267
return handler.read_body_bytes()
3269
def _save_content(self, content):
3270
path = self.branch._remote_path()
3272
response, handler = self.branch._call_with_body_bytes_expecting_body(
3273
'Branch.put_config_file', (path,
3274
self.branch._lock_token, self.branch._repo_lock_token),
3276
except errors.UnknownSmartMethod:
3278
return self._real_store._save_content(content)
3279
handler.cancel_read_body()
3280
if response != ('ok', ):
3281
raise errors.UnexpectedSmartServerResponse(response)
3283
def _ensure_real(self):
3284
self.branch._ensure_real()
3285
if self._real_store is None:
3286
self._real_store = _mod_config.BranchStore(self.branch)
3289
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3290
"""Branch stored on a server accessed by HPSS RPC.
3292
At the moment most operations are mapped down to simple file operations.
3295
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3296
_client=None, format=None, setup_stacking=True, name=None,
3297
possible_transports=None):
3298
"""Create a RemoteBranch instance.
3300
:param real_branch: An optional local implementation of the branch
3301
format, usually accessing the data via the VFS.
3302
:param _client: Private parameter for testing.
3303
:param format: A RemoteBranchFormat object, None to create one
3304
automatically. If supplied it should have a network_name already
3306
:param setup_stacking: If True make an RPC call to determine the
3307
stacked (or not) status of the branch. If False assume the branch
3309
:param name: Colocated branch name
3311
# We intentionally don't call the parent class's __init__, because it
3312
# will try to assign to self.tags, which is a property in this subclass.
3313
# And the parent's __init__ doesn't do much anyway.
3314
self.bzrdir = remote_bzrdir
3316
if _client is not None:
3317
self._client = _client
3319
self._client = remote_bzrdir._client
3320
self.repository = remote_repository
3321
if real_branch is not None:
3322
self._real_branch = real_branch
3323
# Give the remote repository the matching real repo.
3324
real_repo = self._real_branch.repository
3325
if isinstance(real_repo, RemoteRepository):
3326
real_repo._ensure_real()
3327
real_repo = real_repo._real_repository
3328
self.repository._set_real_repository(real_repo)
3329
# Give the branch the remote repository to let fast-pathing happen.
3330
self._real_branch.repository = self.repository
3332
self._real_branch = None
3333
# Fill out expected attributes of branch for breezy API users.
3334
self._clear_cached_state()
3335
# TODO: deprecate self.base in favor of user_url
3336
self.base = self.bzrdir.user_url
3338
self._control_files = None
3339
self._lock_mode = None
3340
self._lock_token = None
3341
self._repo_lock_token = None
3342
self._lock_count = 0
3343
self._leave_lock = False
3344
self.conf_store = None
3345
# Setup a format: note that we cannot call _ensure_real until all the
3346
# attributes above are set: This code cannot be moved higher up in this
3349
self._format = RemoteBranchFormat()
3350
if real_branch is not None:
3351
self._format._network_name = \
3352
self._real_branch._format.network_name()
3354
self._format = format
3355
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3356
# branch.open_branch method.
3357
self._real_ignore_fallbacks = not setup_stacking
3358
if not self._format._network_name:
3359
# Did not get from open_branchV2 - old server.
3361
self._format._network_name = \
3362
self._real_branch._format.network_name()
3363
self.tags = self._format.make_tags(self)
3364
# The base class init is not called, so we duplicate this:
3365
hooks = branch.Branch.hooks['open']
3368
self._is_stacked = False
3370
self._setup_stacking(possible_transports)
3372
def _setup_stacking(self, possible_transports):
3373
# configure stacking into the remote repository, by reading it from
3376
fallback_url = self.get_stacked_on_url()
3377
except (errors.NotStacked, errors.UnstackableBranchFormat,
3378
errors.UnstackableRepositoryFormat) as e:
3380
self._is_stacked = True
3381
if possible_transports is None:
3382
possible_transports = []
3384
possible_transports = list(possible_transports)
3385
possible_transports.append(self.bzrdir.root_transport)
3386
self._activate_fallback_location(fallback_url,
3387
possible_transports=possible_transports)
3389
def _get_config(self):
3390
return RemoteBranchConfig(self)
3392
def _get_config_store(self):
3393
if self.conf_store is None:
3394
self.conf_store = RemoteBranchStore(self)
3395
return self.conf_store
3397
def store_uncommitted(self, creator):
3399
return self._real_branch.store_uncommitted(creator)
3401
def get_unshelver(self, tree):
3403
return self._real_branch.get_unshelver(tree)
3405
def _get_real_transport(self):
3406
# if we try vfs access, return the real branch's vfs transport
3408
return self._real_branch._transport
3410
_transport = property(_get_real_transport)
3413
return "%s(%s)" % (self.__class__.__name__, self.base)
3417
def _ensure_real(self):
3418
"""Ensure that there is a _real_branch set.
3420
Used before calls to self._real_branch.
3422
if self._real_branch is None:
3423
if not vfs.vfs_enabled():
3424
raise AssertionError('smart server vfs must be enabled '
3425
'to use vfs implementation')
3426
self.bzrdir._ensure_real()
3427
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3428
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3429
# The remote branch and the real branch shares the same store. If
3430
# we don't, there will always be cases where one of the stores
3431
# doesn't see an update made on the other.
3432
self._real_branch.conf_store = self.conf_store
3433
if self.repository._real_repository is None:
3434
# Give the remote repository the matching real repo.
3435
real_repo = self._real_branch.repository
3436
if isinstance(real_repo, RemoteRepository):
3437
real_repo._ensure_real()
3438
real_repo = real_repo._real_repository
3439
self.repository._set_real_repository(real_repo)
3440
# Give the real branch the remote repository to let fast-pathing
3442
self._real_branch.repository = self.repository
3443
if self._lock_mode == 'r':
3444
self._real_branch.lock_read()
3445
elif self._lock_mode == 'w':
3446
self._real_branch.lock_write(token=self._lock_token)
3448
def _translate_error(self, err, **context):
3449
self.repository._translate_error(err, branch=self, **context)
3451
def _clear_cached_state(self):
3452
super(RemoteBranch, self)._clear_cached_state()
3453
if self._real_branch is not None:
3454
self._real_branch._clear_cached_state()
3456
def _clear_cached_state_of_remote_branch_only(self):
3457
"""Like _clear_cached_state, but doesn't clear the cache of
3460
This is useful when falling back to calling a method of
3461
self._real_branch that changes state. In that case the underlying
3462
branch changes, so we need to invalidate this RemoteBranch's cache of
3463
it. However, there's no need to invalidate the _real_branch's cache
3464
too, in fact doing so might harm performance.
3466
super(RemoteBranch, self)._clear_cached_state()
3469
def control_files(self):
3470
# Defer actually creating RemoteBranchLockableFiles until its needed,
3471
# because it triggers an _ensure_real that we otherwise might not need.
3472
if self._control_files is None:
3473
self._control_files = RemoteBranchLockableFiles(
3474
self.bzrdir, self._client)
3475
return self._control_files
3477
def get_physical_lock_status(self):
3478
"""See Branch.get_physical_lock_status()."""
3480
response = self._client.call('Branch.get_physical_lock_status',
3481
self._remote_path())
3482
except errors.UnknownSmartMethod:
3484
return self._real_branch.get_physical_lock_status()
3485
if response[0] not in ('yes', 'no'):
3486
raise errors.UnexpectedSmartServerResponse(response)
3487
return (response[0] == 'yes')
3489
def get_stacked_on_url(self):
3490
"""Get the URL this branch is stacked against.
3492
:raises NotStacked: If the branch is not stacked.
3493
:raises UnstackableBranchFormat: If the branch does not support
3495
:raises UnstackableRepositoryFormat: If the repository does not support
3499
# there may not be a repository yet, so we can't use
3500
# self._translate_error, so we can't use self._call either.
3501
response = self._client.call('Branch.get_stacked_on_url',
3502
self._remote_path())
3503
except errors.ErrorFromSmartServer as err:
3504
# there may not be a repository yet, so we can't call through
3505
# its _translate_error
3506
_translate_error(err, branch=self)
3507
except errors.UnknownSmartMethod as err:
3509
return self._real_branch.get_stacked_on_url()
3510
if response[0] != 'ok':
3511
raise errors.UnexpectedSmartServerResponse(response)
3514
def set_stacked_on_url(self, url):
3515
branch.Branch.set_stacked_on_url(self, url)
3516
# We need the stacked_on_url to be visible both locally (to not query
3517
# it repeatedly) and remotely (so smart verbs can get it server side)
3518
# Without the following line,
3519
# breezy.tests.per_branch.test_create_clone.TestCreateClone
3520
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3521
# fails for remote branches -- vila 2012-01-04
3522
self.conf_store.save_changes()
3524
self._is_stacked = False
3526
self._is_stacked = True
3528
def _vfs_get_tags_bytes(self):
3530
return self._real_branch._get_tags_bytes()
3533
def _get_tags_bytes(self):
3534
if self._tags_bytes is None:
3535
self._tags_bytes = self._get_tags_bytes_via_hpss()
3536
return self._tags_bytes
3538
def _get_tags_bytes_via_hpss(self):
3539
medium = self._client._medium
3540
if medium._is_remote_before((1, 13)):
3541
return self._vfs_get_tags_bytes()
3543
response = self._call('Branch.get_tags_bytes', self._remote_path())
3544
except errors.UnknownSmartMethod:
3545
medium._remember_remote_is_before((1, 13))
3546
return self._vfs_get_tags_bytes()
3549
def _vfs_set_tags_bytes(self, bytes):
3551
return self._real_branch._set_tags_bytes(bytes)
3553
def _set_tags_bytes(self, bytes):
3554
if self.is_locked():
3555
self._tags_bytes = bytes
3556
medium = self._client._medium
3557
if medium._is_remote_before((1, 18)):
3558
self._vfs_set_tags_bytes(bytes)
3562
self._remote_path(), self._lock_token, self._repo_lock_token)
3563
response = self._call_with_body_bytes(
3564
'Branch.set_tags_bytes', args, bytes)
3565
except errors.UnknownSmartMethod:
3566
medium._remember_remote_is_before((1, 18))
3567
self._vfs_set_tags_bytes(bytes)
3569
def lock_read(self):
3570
"""Lock the branch for read operations.
3572
:return: A breezy.lock.LogicalLockResult.
3574
self.repository.lock_read()
3575
if not self._lock_mode:
3576
self._note_lock('r')
3577
self._lock_mode = 'r'
3578
self._lock_count = 1
3579
if self._real_branch is not None:
3580
self._real_branch.lock_read()
3582
self._lock_count += 1
3583
return lock.LogicalLockResult(self.unlock)
3585
def _remote_lock_write(self, token):
3587
branch_token = repo_token = ''
3589
branch_token = token
3590
repo_token = self.repository.lock_write().repository_token
3591
self.repository.unlock()
3592
err_context = {'token': token}
3594
response = self._call(
3595
'Branch.lock_write', self._remote_path(), branch_token,
3596
repo_token or '', **err_context)
3597
except errors.LockContention as e:
3598
# The LockContention from the server doesn't have any
3599
# information about the lock_url. We re-raise LockContention
3600
# with valid lock_url.
3601
raise errors.LockContention('(remote lock)',
3602
self.repository.base.split('.bzr/')[0])
3603
if response[0] != 'ok':
3604
raise errors.UnexpectedSmartServerResponse(response)
3605
ok, branch_token, repo_token = response
3606
return branch_token, repo_token
3608
def lock_write(self, token=None):
3609
if not self._lock_mode:
3610
self._note_lock('w')
3611
# Lock the branch and repo in one remote call.
3612
remote_tokens = self._remote_lock_write(token)
3613
self._lock_token, self._repo_lock_token = remote_tokens
3614
if not self._lock_token:
3615
raise SmartProtocolError('Remote server did not return a token!')
3616
# Tell the self.repository object that it is locked.
3617
self.repository.lock_write(
3618
self._repo_lock_token, _skip_rpc=True)
3620
if self._real_branch is not None:
3621
self._real_branch.lock_write(token=self._lock_token)
3622
if token is not None:
3623
self._leave_lock = True
3625
self._leave_lock = False
3626
self._lock_mode = 'w'
3627
self._lock_count = 1
3628
elif self._lock_mode == 'r':
3629
raise errors.ReadOnlyError(self)
3631
if token is not None:
3632
# A token was given to lock_write, and we're relocking, so
3633
# check that the given token actually matches the one we
3635
if token != self._lock_token:
3636
raise errors.TokenMismatch(token, self._lock_token)
3637
self._lock_count += 1
3638
# Re-lock the repository too.
3639
self.repository.lock_write(self._repo_lock_token)
3640
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3642
def _unlock(self, branch_token, repo_token):
3643
err_context = {'token': str((branch_token, repo_token))}
3644
response = self._call(
3645
'Branch.unlock', self._remote_path(), branch_token,
3646
repo_token or '', **err_context)
3647
if response == ('ok',):
3649
raise errors.UnexpectedSmartServerResponse(response)
3651
@only_raises(errors.LockNotHeld, errors.LockBroken)
3654
self._lock_count -= 1
3655
if not self._lock_count:
3656
if self.conf_store is not None:
3657
self.conf_store.save_changes()
3658
self._clear_cached_state()
3659
mode = self._lock_mode
3660
self._lock_mode = None
3661
if self._real_branch is not None:
3662
if (not self._leave_lock and mode == 'w' and
3663
self._repo_lock_token):
3664
# If this RemoteBranch will remove the physical lock
3665
# for the repository, make sure the _real_branch
3666
# doesn't do it first. (Because the _real_branch's
3667
# repository is set to be the RemoteRepository.)
3668
self._real_branch.repository.leave_lock_in_place()
3669
self._real_branch.unlock()
3671
# Only write-locked branched need to make a remote method
3672
# call to perform the unlock.
3674
if not self._lock_token:
3675
raise AssertionError('Locked, but no token!')
3676
branch_token = self._lock_token
3677
repo_token = self._repo_lock_token
3678
self._lock_token = None
3679
self._repo_lock_token = None
3680
if not self._leave_lock:
3681
self._unlock(branch_token, repo_token)
3683
self.repository.unlock()
3685
def break_lock(self):
3687
response = self._call(
3688
'Branch.break_lock', self._remote_path())
3689
except errors.UnknownSmartMethod:
3691
return self._real_branch.break_lock()
3692
if response != ('ok',):
3693
raise errors.UnexpectedSmartServerResponse(response)
3695
def leave_lock_in_place(self):
3696
if not self._lock_token:
3697
raise NotImplementedError(self.leave_lock_in_place)
3698
self._leave_lock = True
3700
def dont_leave_lock_in_place(self):
3701
if not self._lock_token:
3702
raise NotImplementedError(self.dont_leave_lock_in_place)
3703
self._leave_lock = False
3706
def get_rev_id(self, revno, history=None):
3708
return _mod_revision.NULL_REVISION
3709
last_revision_info = self.last_revision_info()
3710
ok, result = self.repository.get_rev_id_for_revno(
3711
revno, last_revision_info)
3714
missing_parent = result[1]
3715
# Either the revision named by the server is missing, or its parent
3716
# is. Call get_parent_map to determine which, so that we report a
3718
parent_map = self.repository.get_parent_map([missing_parent])
3719
if missing_parent in parent_map:
3720
missing_parent = parent_map[missing_parent]
3721
raise errors.RevisionNotPresent(missing_parent, self.repository)
3723
def _read_last_revision_info(self):
3724
response = self._call('Branch.last_revision_info', self._remote_path())
3725
if response[0] != 'ok':
3726
raise SmartProtocolError('unexpected response code %s' % (response,))
3727
revno = int(response[1])
3728
last_revision = response[2]
3729
return (revno, last_revision)
3731
def _gen_revision_history(self):
3732
"""See Branch._gen_revision_history()."""
3733
if self._is_stacked:
3735
return self._real_branch._gen_revision_history()
3736
response_tuple, response_handler = self._call_expecting_body(
3737
'Branch.revision_history', self._remote_path())
3738
if response_tuple[0] != 'ok':
3739
raise errors.UnexpectedSmartServerResponse(response_tuple)
3740
result = response_handler.read_body_bytes().split('\x00')
3745
def _remote_path(self):
3746
return self.bzrdir._path_for_remote_call(self._client)
3748
def _set_last_revision_descendant(self, revision_id, other_branch,
3749
allow_diverged=False, allow_overwrite_descendant=False):
3750
# This performs additional work to meet the hook contract; while its
3751
# undesirable, we have to synthesise the revno to call the hook, and
3752
# not calling the hook is worse as it means changes can't be prevented.
3753
# Having calculated this though, we can't just call into
3754
# set_last_revision_info as a simple call, because there is a set_rh
3755
# hook that some folk may still be using.
3756
old_revno, old_revid = self.last_revision_info()
3757
history = self._lefthand_history(revision_id)
3758
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3759
err_context = {'other_branch': other_branch}
3760
response = self._call('Branch.set_last_revision_ex',
3761
self._remote_path(), self._lock_token, self._repo_lock_token,
3762
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3764
self._clear_cached_state()
3765
if len(response) != 3 and response[0] != 'ok':
3766
raise errors.UnexpectedSmartServerResponse(response)
3767
new_revno, new_revision_id = response[1:]
3768
self._last_revision_info_cache = new_revno, new_revision_id
3769
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3770
if self._real_branch is not None:
3771
cache = new_revno, new_revision_id
3772
self._real_branch._last_revision_info_cache = cache
3774
def _set_last_revision(self, revision_id):
3775
old_revno, old_revid = self.last_revision_info()
3776
# This performs additional work to meet the hook contract; while its
3777
# undesirable, we have to synthesise the revno to call the hook, and
3778
# not calling the hook is worse as it means changes can't be prevented.
3779
# Having calculated this though, we can't just call into
3780
# set_last_revision_info as a simple call, because there is a set_rh
3781
# hook that some folk may still be using.
3782
history = self._lefthand_history(revision_id)
3783
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3784
self._clear_cached_state()
3785
response = self._call('Branch.set_last_revision',
3786
self._remote_path(), self._lock_token, self._repo_lock_token,
3788
if response != ('ok',):
3789
raise errors.UnexpectedSmartServerResponse(response)
3790
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3792
def _get_parent_location(self):
3793
medium = self._client._medium
3794
if medium._is_remote_before((1, 13)):
3795
return self._vfs_get_parent_location()
3797
response = self._call('Branch.get_parent', self._remote_path())
3798
except errors.UnknownSmartMethod:
3799
medium._remember_remote_is_before((1, 13))
3800
return self._vfs_get_parent_location()
3801
if len(response) != 1:
3802
raise errors.UnexpectedSmartServerResponse(response)
3803
parent_location = response[0]
3804
if parent_location == '':
3806
return parent_location
3808
def _vfs_get_parent_location(self):
3810
return self._real_branch._get_parent_location()
3812
def _set_parent_location(self, url):
3813
medium = self._client._medium
3814
if medium._is_remote_before((1, 15)):
3815
return self._vfs_set_parent_location(url)
3817
call_url = url or ''
3818
if not isinstance(call_url, str):
3819
raise AssertionError('url must be a str or None (%s)' % url)
3820
response = self._call('Branch.set_parent_location',
3821
self._remote_path(), self._lock_token, self._repo_lock_token,
3823
except errors.UnknownSmartMethod:
3824
medium._remember_remote_is_before((1, 15))
3825
return self._vfs_set_parent_location(url)
3827
raise errors.UnexpectedSmartServerResponse(response)
3829
def _vfs_set_parent_location(self, url):
3831
return self._real_branch._set_parent_location(url)
3834
def pull(self, source, overwrite=False, stop_revision=None,
3836
self._clear_cached_state_of_remote_branch_only()
3838
return self._real_branch.pull(
3839
source, overwrite=overwrite, stop_revision=stop_revision,
3840
_override_hook_target=self, **kwargs)
3843
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3845
return self._real_branch.push(
3846
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3847
_override_hook_source_branch=self)
3849
def peek_lock_mode(self):
3850
return self._lock_mode
3852
def is_locked(self):
3853
return self._lock_count >= 1
3856
def revision_id_to_dotted_revno(self, revision_id):
3857
"""Given a revision id, return its dotted revno.
3859
:return: a tuple like (1,) or (400,1,3).
3862
response = self._call('Branch.revision_id_to_revno',
3863
self._remote_path(), revision_id)
3864
except errors.UnknownSmartMethod:
3866
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3867
if response[0] == 'ok':
3868
return tuple([int(x) for x in response[1:]])
3870
raise errors.UnexpectedSmartServerResponse(response)
3873
def revision_id_to_revno(self, revision_id):
3874
"""Given a revision id on the branch mainline, return its revno.
3879
response = self._call('Branch.revision_id_to_revno',
3880
self._remote_path(), revision_id)
3881
except errors.UnknownSmartMethod:
3883
return self._real_branch.revision_id_to_revno(revision_id)
3884
if response[0] == 'ok':
3885
if len(response) == 2:
3886
return int(response[1])
3887
raise NoSuchRevision(self, revision_id)
3889
raise errors.UnexpectedSmartServerResponse(response)
3892
def set_last_revision_info(self, revno, revision_id):
3893
# XXX: These should be returned by the set_last_revision_info verb
3894
old_revno, old_revid = self.last_revision_info()
3895
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3896
if not revision_id or not isinstance(revision_id, basestring):
3897
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3899
response = self._call('Branch.set_last_revision_info',
3900
self._remote_path(), self._lock_token, self._repo_lock_token,
3901
str(revno), revision_id)
3902
except errors.UnknownSmartMethod:
3904
self._clear_cached_state_of_remote_branch_only()
3905
self._real_branch.set_last_revision_info(revno, revision_id)
3906
self._last_revision_info_cache = revno, revision_id
3908
if response == ('ok',):
3909
self._clear_cached_state()
3910
self._last_revision_info_cache = revno, revision_id
3911
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3912
# Update the _real_branch's cache too.
3913
if self._real_branch is not None:
3914
cache = self._last_revision_info_cache
3915
self._real_branch._last_revision_info_cache = cache
3917
raise errors.UnexpectedSmartServerResponse(response)
3920
def generate_revision_history(self, revision_id, last_rev=None,
3922
medium = self._client._medium
3923
if not medium._is_remote_before((1, 6)):
3924
# Use a smart method for 1.6 and above servers
3926
self._set_last_revision_descendant(revision_id, other_branch,
3927
allow_diverged=True, allow_overwrite_descendant=True)
3929
except errors.UnknownSmartMethod:
3930
medium._remember_remote_is_before((1, 6))
3931
self._clear_cached_state_of_remote_branch_only()
3932
graph = self.repository.get_graph()
3933
(last_revno, last_revid) = self.last_revision_info()
3934
known_revision_ids = [
3935
(last_revid, last_revno),
3936
(_mod_revision.NULL_REVISION, 0),
3938
if last_rev is not None:
3939
if not graph.is_ancestor(last_rev, revision_id):
3940
# our previous tip is not merged into stop_revision
3941
raise errors.DivergedBranches(self, other_branch)
3942
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
3943
self.set_last_revision_info(revno, revision_id)
3945
def set_push_location(self, location):
3946
self._set_config_location('push_location', location)
3948
def heads_to_fetch(self):
3949
if self._format._use_default_local_heads_to_fetch():
3950
# We recognise this format, and its heads-to-fetch implementation
3951
# is the default one (tip + tags). In this case it's cheaper to
3952
# just use the default implementation rather than a special RPC as
3953
# the tip and tags data is cached.
3954
return branch.Branch.heads_to_fetch(self)
3955
medium = self._client._medium
3956
if medium._is_remote_before((2, 4)):
3957
return self._vfs_heads_to_fetch()
3959
return self._rpc_heads_to_fetch()
3960
except errors.UnknownSmartMethod:
3961
medium._remember_remote_is_before((2, 4))
3962
return self._vfs_heads_to_fetch()
3964
def _rpc_heads_to_fetch(self):
3965
response = self._call('Branch.heads_to_fetch', self._remote_path())
3966
if len(response) != 2:
3967
raise errors.UnexpectedSmartServerResponse(response)
3968
must_fetch, if_present_fetch = response
3969
return set(must_fetch), set(if_present_fetch)
3971
def _vfs_heads_to_fetch(self):
3973
return self._real_branch.heads_to_fetch()
3976
class RemoteConfig(object):
3977
"""A Config that reads and writes from smart verbs.
3979
It is a low-level object that considers config data to be name/value pairs
3980
that may be associated with a section. Assigning meaning to the these
3981
values is done at higher levels like breezy.config.TreeConfig.
3984
def get_option(self, name, section=None, default=None):
3985
"""Return the value associated with a named option.
3987
:param name: The name of the value
3988
:param section: The section the option is in (if any)
3989
:param default: The value to return if the value is not set
3990
:return: The value or default value
3993
configobj = self._get_configobj()
3996
section_obj = configobj
3999
section_obj = configobj[section]
4002
if section_obj is None:
4005
value = section_obj.get(name, default)
4006
except errors.UnknownSmartMethod:
4007
value = self._vfs_get_option(name, section, default)
4008
for hook in _mod_config.OldConfigHooks['get']:
4009
hook(self, name, value)
4012
def _response_to_configobj(self, response):
4013
if len(response[0]) and response[0][0] != 'ok':
4014
raise errors.UnexpectedSmartServerResponse(response)
4015
lines = response[1].read_body_bytes().splitlines()
4016
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4017
for hook in _mod_config.OldConfigHooks['load']:
4022
class RemoteBranchConfig(RemoteConfig):
4023
"""A RemoteConfig for Branches."""
4025
def __init__(self, branch):
4026
self._branch = branch
4028
def _get_configobj(self):
4029
path = self._branch._remote_path()
4030
response = self._branch._client.call_expecting_body(
4031
'Branch.get_config_file', path)
4032
return self._response_to_configobj(response)
4034
def set_option(self, value, name, section=None):
4035
"""Set the value associated with a named option.
4037
:param value: The value to set
4038
:param name: The name of the value to set
4039
:param section: The section the option is in (if any)
4041
medium = self._branch._client._medium
4042
if medium._is_remote_before((1, 14)):
4043
return self._vfs_set_option(value, name, section)
4044
if isinstance(value, dict):
4045
if medium._is_remote_before((2, 2)):
4046
return self._vfs_set_option(value, name, section)
4047
return self._set_config_option_dict(value, name, section)
4049
return self._set_config_option(value, name, section)
4051
def _set_config_option(self, value, name, section):
4053
path = self._branch._remote_path()
4054
response = self._branch._client.call('Branch.set_config_option',
4055
path, self._branch._lock_token, self._branch._repo_lock_token,
4056
value.encode('utf8'), name, section or '')
4057
except errors.UnknownSmartMethod:
4058
medium = self._branch._client._medium
4059
medium._remember_remote_is_before((1, 14))
4060
return self._vfs_set_option(value, name, section)
4062
raise errors.UnexpectedSmartServerResponse(response)
4064
def _serialize_option_dict(self, option_dict):
4066
for key, value in option_dict.items():
4067
if isinstance(key, unicode):
4068
key = key.encode('utf8')
4069
if isinstance(value, unicode):
4070
value = value.encode('utf8')
4071
utf8_dict[key] = value
4072
return bencode.bencode(utf8_dict)
4074
def _set_config_option_dict(self, value, name, section):
4076
path = self._branch._remote_path()
4077
serialised_dict = self._serialize_option_dict(value)
4078
response = self._branch._client.call(
4079
'Branch.set_config_option_dict',
4080
path, self._branch._lock_token, self._branch._repo_lock_token,
4081
serialised_dict, name, section or '')
4082
except errors.UnknownSmartMethod:
4083
medium = self._branch._client._medium
4084
medium._remember_remote_is_before((2, 2))
4085
return self._vfs_set_option(value, name, section)
4087
raise errors.UnexpectedSmartServerResponse(response)
4089
def _real_object(self):
4090
self._branch._ensure_real()
4091
return self._branch._real_branch
4093
def _vfs_set_option(self, value, name, section=None):
4094
return self._real_object()._get_config().set_option(
4095
value, name, section)
4098
class RemoteBzrDirConfig(RemoteConfig):
4099
"""A RemoteConfig for BzrDirs."""
4101
def __init__(self, bzrdir):
4102
self._bzrdir = bzrdir
4104
def _get_configobj(self):
4105
medium = self._bzrdir._client._medium
4106
verb = 'BzrDir.get_config_file'
4107
if medium._is_remote_before((1, 15)):
4108
raise errors.UnknownSmartMethod(verb)
4109
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4110
response = self._bzrdir._call_expecting_body(
4112
return self._response_to_configobj(response)
4114
def _vfs_get_option(self, name, section, default):
4115
return self._real_object()._get_config().get_option(
4116
name, section, default)
4118
def set_option(self, value, name, section=None):
4119
"""Set the value associated with a named option.
4121
:param value: The value to set
4122
:param name: The name of the value to set
4123
:param section: The section the option is in (if any)
4125
return self._real_object()._get_config().set_option(
4126
value, name, section)
4128
def _real_object(self):
4129
self._bzrdir._ensure_real()
4130
return self._bzrdir._real_bzrdir
4133
def _extract_tar(tar, to_dir):
4134
"""Extract all the contents of a tarfile object.
4136
A replacement for extractall, which is not present in python2.4
4139
tar.extract(tarinfo, to_dir)
4142
error_translators = registry.Registry()
4143
no_context_error_translators = registry.Registry()
4146
def _translate_error(err, **context):
4147
"""Translate an ErrorFromSmartServer into a more useful error.
4149
Possible context keys:
4157
If the error from the server doesn't match a known pattern, then
4158
UnknownErrorFromSmartServer is raised.
4162
return context[name]
4163
except KeyError as key_err:
4164
mutter('Missing key %r in context %r', key_err.args[0], context)
4167
"""Get the path from the context if present, otherwise use first error
4171
return context['path']
4172
except KeyError as key_err:
4174
return err.error_args[0]
4175
except IndexError as idx_err:
4177
'Missing key %r in context %r', key_err.args[0], context)
4181
translator = error_translators.get(err.error_verb)
4185
raise translator(err, find, get_path)
4187
translator = no_context_error_translators.get(err.error_verb)
4189
raise errors.UnknownErrorFromSmartServer(err)
4191
raise translator(err)
4194
error_translators.register('NoSuchRevision',
4195
lambda err, find, get_path: NoSuchRevision(
4196
find('branch'), err.error_args[0]))
4197
error_translators.register('nosuchrevision',
4198
lambda err, find, get_path: NoSuchRevision(
4199
find('repository'), err.error_args[0]))
4201
def _translate_nobranch_error(err, find, get_path):
4202
if len(err.error_args) >= 1:
4203
extra = err.error_args[0]
4206
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4209
error_translators.register('nobranch', _translate_nobranch_error)
4210
error_translators.register('norepository',
4211
lambda err, find, get_path: errors.NoRepositoryPresent(
4213
error_translators.register('UnlockableTransport',
4214
lambda err, find, get_path: errors.UnlockableTransport(
4215
find('bzrdir').root_transport))
4216
error_translators.register('TokenMismatch',
4217
lambda err, find, get_path: errors.TokenMismatch(
4218
find('token'), '(remote token)'))
4219
error_translators.register('Diverged',
4220
lambda err, find, get_path: errors.DivergedBranches(
4221
find('branch'), find('other_branch')))
4222
error_translators.register('NotStacked',
4223
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4225
def _translate_PermissionDenied(err, find, get_path):
4227
if len(err.error_args) >= 2:
4228
extra = err.error_args[1]
4231
return errors.PermissionDenied(path, extra=extra)
4233
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4234
error_translators.register('ReadError',
4235
lambda err, find, get_path: errors.ReadError(get_path()))
4236
error_translators.register('NoSuchFile',
4237
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4238
error_translators.register('TokenLockingNotSupported',
4239
lambda err, find, get_path: errors.TokenLockingNotSupported(
4240
find('repository')))
4241
error_translators.register('UnsuspendableWriteGroup',
4242
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4243
repository=find('repository')))
4244
error_translators.register('UnresumableWriteGroup',
4245
lambda err, find, get_path: errors.UnresumableWriteGroup(
4246
repository=find('repository'), write_groups=err.error_args[0],
4247
reason=err.error_args[1]))
4248
no_context_error_translators.register('IncompatibleRepositories',
4249
lambda err: errors.IncompatibleRepositories(
4250
err.error_args[0], err.error_args[1], err.error_args[2]))
4251
no_context_error_translators.register('LockContention',
4252
lambda err: errors.LockContention('(remote lock)'))
4253
no_context_error_translators.register('LockFailed',
4254
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4255
no_context_error_translators.register('TipChangeRejected',
4256
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4257
no_context_error_translators.register('UnstackableBranchFormat',
4258
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4259
no_context_error_translators.register('UnstackableRepositoryFormat',
4260
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4261
no_context_error_translators.register('FileExists',
4262
lambda err: errors.FileExists(err.error_args[0]))
4263
no_context_error_translators.register('DirectoryNotEmpty',
4264
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4266
def _translate_short_readv_error(err):
4267
args = err.error_args
4268
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4271
no_context_error_translators.register('ShortReadvError',
4272
_translate_short_readv_error)
4274
def _translate_unicode_error(err):
4275
encoding = str(err.error_args[0]) # encoding must always be a string
4276
val = err.error_args[1]
4277
start = int(err.error_args[2])
4278
end = int(err.error_args[3])
4279
reason = str(err.error_args[4]) # reason must always be a string
4280
if val.startswith('u:'):
4281
val = val[2:].decode('utf-8')
4282
elif val.startswith('s:'):
4283
val = val[2:].decode('base64')
4284
if err.error_verb == 'UnicodeDecodeError':
4285
raise UnicodeDecodeError(encoding, val, start, end, reason)
4286
elif err.error_verb == 'UnicodeEncodeError':
4287
raise UnicodeEncodeError(encoding, val, start, end, reason)
4289
no_context_error_translators.register('UnicodeEncodeError',
4290
_translate_unicode_error)
4291
no_context_error_translators.register('UnicodeDecodeError',
4292
_translate_unicode_error)
4293
no_context_error_translators.register('ReadOnlyError',
4294
lambda err: errors.TransportNotPossible('readonly transport'))
4295
no_context_error_translators.register('MemoryError',
4296
lambda err: errors.BzrError("remote server out of memory\n"
4297
"Retry non-remotely, or contact the server admin for details."))
4298
no_context_error_translators.register('RevisionNotPresent',
4299
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4301
no_context_error_translators.register('BzrCheckError',
4302
lambda err: errors.BzrCheckError(msg=err.error_args[0]))