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
26
bzrdir as _mod_bzrdir,
27
config as _mod_config,
38
repository as _mod_repository,
39
revision as _mod_revision,
41
testament as _mod_testament,
46
from .bzrbranch import BranchReferenceFormat
47
from .branch import BranchWriteLockResult
48
from .decorators import needs_read_lock, needs_write_lock, only_raises
53
from .i18n import gettext
54
from .inventory import Inventory
55
from .lockable_files import LockableFiles
60
from .smart import client, vfs, repository as smart_repo
61
from .smart.client import _SmartClient
62
from .revision import NULL_REVISION
63
from .revisiontree import InventoryRevisionTree
64
from .repository import RepositoryWriteLockResult, _LazyListJoin
65
from .serializer import format_registry as serializer_format_registry
66
from .trace import mutter, note, warning, log_exception_quietly
67
from .versionedfile import FulltextContentFactory
70
_DEFAULT_SEARCH_DEPTH = 100
73
class _RpcHelper(object):
74
"""Mixin class that helps with issuing RPCs."""
76
def _call(self, method, *args, **err_context):
78
return self._client.call(method, *args)
79
except errors.ErrorFromSmartServer as err:
80
self._translate_error(err, **err_context)
82
def _call_expecting_body(self, method, *args, **err_context):
84
return self._client.call_expecting_body(method, *args)
85
except errors.ErrorFromSmartServer as err:
86
self._translate_error(err, **err_context)
88
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
90
return self._client.call_with_body_bytes(method, args, body_bytes)
91
except errors.ErrorFromSmartServer as err:
92
self._translate_error(err, **err_context)
94
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
97
return self._client.call_with_body_bytes_expecting_body(
98
method, args, body_bytes)
99
except errors.ErrorFromSmartServer as err:
100
self._translate_error(err, **err_context)
103
def response_tuple_to_repo_format(response):
104
"""Convert a response tuple describing a repository format to a format."""
105
format = RemoteRepositoryFormat()
106
format._rich_root_data = (response[0] == 'yes')
107
format._supports_tree_reference = (response[1] == 'yes')
108
format._supports_external_lookups = (response[2] == 'yes')
109
format._network_name = response[3]
113
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.remote
114
# does not have to be imported unless a remote format is involved.
116
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
117
"""Format representing bzrdirs accessed via a smart server"""
119
supports_workingtrees = False
121
colocated_branches = False
124
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
125
# XXX: It's a bit ugly that the network name is here, because we'd
126
# like to believe that format objects are stateless or at least
127
# immutable, However, we do at least avoid mutating the name after
128
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
129
self._network_name = None
132
return "%s(_network_name=%r)" % (self.__class__.__name__,
135
def get_format_description(self):
136
if self._network_name:
138
real_format = controldir.network_format_registry.get(
143
return 'Remote: ' + real_format.get_format_description()
144
return 'bzr remote bzrdir'
146
def get_format_string(self):
147
raise NotImplementedError(self.get_format_string)
149
def network_name(self):
150
if self._network_name:
151
return self._network_name
153
raise AssertionError("No network name set.")
155
def initialize_on_transport(self, transport):
157
# hand off the request to the smart server
158
client_medium = transport.get_smart_medium()
159
except errors.NoSmartMedium:
160
# TODO: lookup the local format from a server hint.
161
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
162
return local_dir_format.initialize_on_transport(transport)
163
client = _SmartClient(client_medium)
164
path = client.remote_path_from_transport(transport)
166
response = client.call('BzrDirFormat.initialize', path)
167
except errors.ErrorFromSmartServer as err:
168
_translate_error(err, path=path)
169
if response[0] != 'ok':
170
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
171
format = RemoteBzrDirFormat()
172
self._supply_sub_formats_to(format)
173
return RemoteBzrDir(transport, format)
175
def parse_NoneTrueFalse(self, arg):
182
raise AssertionError("invalid arg %r" % arg)
184
def _serialize_NoneTrueFalse(self, arg):
191
def _serialize_NoneString(self, arg):
194
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
195
create_prefix=False, force_new_repo=False, stacked_on=None,
196
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
199
# hand off the request to the smart server
200
client_medium = transport.get_smart_medium()
201
except errors.NoSmartMedium:
204
# Decline to open it if the server doesn't support our required
205
# version (3) so that the VFS-based transport will do it.
206
if client_medium.should_probe():
208
server_version = client_medium.protocol_version()
209
if server_version != '2':
213
except errors.SmartProtocolError:
214
# Apparently there's no usable smart server there, even though
215
# the medium supports the smart protocol.
220
client = _SmartClient(client_medium)
221
path = client.remote_path_from_transport(transport)
222
if client_medium._is_remote_before((1, 16)):
225
# TODO: lookup the local format from a server hint.
226
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
227
self._supply_sub_formats_to(local_dir_format)
228
return local_dir_format.initialize_on_transport_ex(transport,
229
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
230
force_new_repo=force_new_repo, stacked_on=stacked_on,
231
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
232
make_working_trees=make_working_trees, shared_repo=shared_repo,
234
return self._initialize_on_transport_ex_rpc(client, path, transport,
235
use_existing_dir, create_prefix, force_new_repo, stacked_on,
236
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
238
def _initialize_on_transport_ex_rpc(self, client, path, transport,
239
use_existing_dir, create_prefix, force_new_repo, stacked_on,
240
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
242
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
243
args.append(self._serialize_NoneTrueFalse(create_prefix))
244
args.append(self._serialize_NoneTrueFalse(force_new_repo))
245
args.append(self._serialize_NoneString(stacked_on))
246
# stack_on_pwd is often/usually our transport
249
stack_on_pwd = transport.relpath(stack_on_pwd)
252
except errors.PathNotChild:
254
args.append(self._serialize_NoneString(stack_on_pwd))
255
args.append(self._serialize_NoneString(repo_format_name))
256
args.append(self._serialize_NoneTrueFalse(make_working_trees))
257
args.append(self._serialize_NoneTrueFalse(shared_repo))
258
request_network_name = self._network_name or \
259
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
261
response = client.call('BzrDirFormat.initialize_ex_1.16',
262
request_network_name, path, *args)
263
except errors.UnknownSmartMethod:
264
client._medium._remember_remote_is_before((1,16))
265
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
266
self._supply_sub_formats_to(local_dir_format)
267
return local_dir_format.initialize_on_transport_ex(transport,
268
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
269
force_new_repo=force_new_repo, stacked_on=stacked_on,
270
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
271
make_working_trees=make_working_trees, shared_repo=shared_repo,
273
except errors.ErrorFromSmartServer as err:
274
_translate_error(err, path=path)
275
repo_path = response[0]
276
bzrdir_name = response[6]
277
require_stacking = response[7]
278
require_stacking = self.parse_NoneTrueFalse(require_stacking)
279
format = RemoteBzrDirFormat()
280
format._network_name = bzrdir_name
281
self._supply_sub_formats_to(format)
282
bzrdir = RemoteBzrDir(transport, format, _client=client)
284
repo_format = response_tuple_to_repo_format(response[1:])
288
repo_bzrdir_format = RemoteBzrDirFormat()
289
repo_bzrdir_format._network_name = response[5]
290
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
294
final_stack = response[8] or None
295
final_stack_pwd = response[9] or None
297
final_stack_pwd = urlutils.join(
298
transport.base, final_stack_pwd)
299
remote_repo = RemoteRepository(repo_bzr, repo_format)
300
if len(response) > 10:
301
# Updated server verb that locks remotely.
302
repo_lock_token = response[10] or None
303
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
305
remote_repo.dont_leave_lock_in_place()
307
remote_repo.lock_write()
308
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
309
final_stack_pwd, require_stacking)
310
policy.acquire_repository()
314
bzrdir._format.set_branch_format(self.get_branch_format())
316
# The repo has already been created, but we need to make sure that
317
# we'll make a stackable branch.
318
bzrdir._format.require_stacking(_skip_repo=True)
319
return remote_repo, bzrdir, require_stacking, policy
321
def _open(self, transport):
322
return RemoteBzrDir(transport, self)
324
def __eq__(self, other):
325
if not isinstance(other, RemoteBzrDirFormat):
327
return self.get_format_description() == other.get_format_description()
329
def __return_repository_format(self):
330
# Always return a RemoteRepositoryFormat object, but if a specific bzr
331
# repository format has been asked for, tell the RemoteRepositoryFormat
332
# that it should use that for init() etc.
333
result = RemoteRepositoryFormat()
334
custom_format = getattr(self, '_repository_format', None)
336
if isinstance(custom_format, RemoteRepositoryFormat):
339
# We will use the custom format to create repositories over the
340
# wire; expose its details like rich_root_data for code to
342
result._custom_format = custom_format
345
def get_branch_format(self):
346
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
347
if not isinstance(result, RemoteBranchFormat):
348
new_result = RemoteBranchFormat()
349
new_result._custom_format = result
351
self.set_branch_format(new_result)
355
repository_format = property(__return_repository_format,
356
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
359
class RemoteControlStore(_mod_config.IniFileStore):
360
"""Control store which attempts to use HPSS calls to retrieve control store.
362
Note that this is specific to bzr-based formats.
365
def __init__(self, bzrdir):
366
super(RemoteControlStore, self).__init__()
368
self._real_store = None
370
def lock_write(self, token=None):
372
return self._real_store.lock_write(token)
376
return self._real_store.unlock()
380
# We need to be able to override the undecorated implementation
381
self.save_without_locking()
383
def save_without_locking(self):
384
super(RemoteControlStore, self).save()
386
def _ensure_real(self):
387
self.bzrdir._ensure_real()
388
if self._real_store is None:
389
self._real_store = _mod_config.ControlStore(self.bzrdir)
391
def external_url(self):
392
return urlutils.join(self.branch.user_url, 'control.conf')
394
def _load_content(self):
395
medium = self.bzrdir._client._medium
396
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
398
response, handler = self.bzrdir._call_expecting_body(
399
'BzrDir.get_config_file', path)
400
except errors.UnknownSmartMethod:
402
return self._real_store._load_content()
403
if len(response) and response[0] != 'ok':
404
raise errors.UnexpectedSmartServerResponse(response)
405
return handler.read_body_bytes()
407
def _save_content(self, content):
408
# FIXME JRV 2011-11-22: Ideally this should use a
409
# HPSS call too, but at the moment it is not possible
410
# to write lock control directories.
412
return self._real_store._save_content(content)
415
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
416
"""Control directory on a remote server, accessed via bzr:// or similar."""
418
def __init__(self, transport, format, _client=None, _force_probe=False):
419
"""Construct a RemoteBzrDir.
421
:param _client: Private parameter for testing. Disables probing and the
422
use of a real bzrdir.
424
_mod_bzrdir.BzrDir.__init__(self, transport, format)
425
# this object holds a delegated bzrdir that uses file-level operations
426
# to talk to the other side
427
self._real_bzrdir = None
428
self._has_working_tree = None
429
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
430
# create_branch for details.
431
self._next_open_branch_result = None
434
medium = transport.get_smart_medium()
435
self._client = client._SmartClient(medium)
437
self._client = _client
444
return '%s(%r)' % (self.__class__.__name__, self._client)
446
def _probe_bzrdir(self):
447
medium = self._client._medium
448
path = self._path_for_remote_call(self._client)
449
if medium._is_remote_before((2, 1)):
453
self._rpc_open_2_1(path)
455
except errors.UnknownSmartMethod:
456
medium._remember_remote_is_before((2, 1))
459
def _rpc_open_2_1(self, path):
460
response = self._call('BzrDir.open_2.1', path)
461
if response == ('no',):
462
raise errors.NotBranchError(path=self.root_transport.base)
463
elif response[0] == 'yes':
464
if response[1] == 'yes':
465
self._has_working_tree = True
466
elif response[1] == 'no':
467
self._has_working_tree = False
469
raise errors.UnexpectedSmartServerResponse(response)
471
raise errors.UnexpectedSmartServerResponse(response)
473
def _rpc_open(self, path):
474
response = self._call('BzrDir.open', path)
475
if response not in [('yes',), ('no',)]:
476
raise errors.UnexpectedSmartServerResponse(response)
477
if response == ('no',):
478
raise errors.NotBranchError(path=self.root_transport.base)
480
def _ensure_real(self):
481
"""Ensure that there is a _real_bzrdir set.
483
Used before calls to self._real_bzrdir.
485
if not self._real_bzrdir:
486
if 'hpssvfs' in debug.debug_flags:
488
warning('VFS BzrDir access triggered\n%s',
489
''.join(traceback.format_stack()))
490
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
491
self.root_transport, probers=[_mod_bzrdir.BzrProber])
492
self._format._network_name = \
493
self._real_bzrdir._format.network_name()
495
def _translate_error(self, err, **context):
496
_translate_error(err, bzrdir=self, **context)
498
def break_lock(self):
499
# Prevent aliasing problems in the next_open_branch_result cache.
500
# See create_branch for rationale.
501
self._next_open_branch_result = None
502
return _mod_bzrdir.BzrDir.break_lock(self)
504
def _vfs_checkout_metadir(self):
506
return self._real_bzrdir.checkout_metadir()
508
def checkout_metadir(self):
509
"""Retrieve the controldir format to use for checkouts of this one.
511
medium = self._client._medium
512
if medium._is_remote_before((2, 5)):
513
return self._vfs_checkout_metadir()
514
path = self._path_for_remote_call(self._client)
516
response = self._client.call('BzrDir.checkout_metadir',
518
except errors.UnknownSmartMethod:
519
medium._remember_remote_is_before((2, 5))
520
return self._vfs_checkout_metadir()
521
if len(response) != 3:
522
raise errors.UnexpectedSmartServerResponse(response)
523
control_name, repo_name, branch_name = response
525
format = controldir.network_format_registry.get(control_name)
527
raise errors.UnknownFormatError(kind='control',
531
repo_format = _mod_repository.network_format_registry.get(
534
raise errors.UnknownFormatError(kind='repository',
536
format.repository_format = repo_format
539
format.set_branch_format(
540
branch.network_format_registry.get(branch_name))
542
raise errors.UnknownFormatError(kind='branch',
546
def _vfs_cloning_metadir(self, require_stacking=False):
548
return self._real_bzrdir.cloning_metadir(
549
require_stacking=require_stacking)
551
def cloning_metadir(self, require_stacking=False):
552
medium = self._client._medium
553
if medium._is_remote_before((1, 13)):
554
return self._vfs_cloning_metadir(require_stacking=require_stacking)
555
verb = 'BzrDir.cloning_metadir'
560
path = self._path_for_remote_call(self._client)
562
response = self._call(verb, path, stacking)
563
except errors.UnknownSmartMethod:
564
medium._remember_remote_is_before((1, 13))
565
return self._vfs_cloning_metadir(require_stacking=require_stacking)
566
except errors.UnknownErrorFromSmartServer as err:
567
if err.error_tuple != ('BranchReference',):
569
# We need to resolve the branch reference to determine the
570
# cloning_metadir. This causes unnecessary RPCs to open the
571
# referenced branch (and bzrdir, etc) but only when the caller
572
# didn't already resolve the branch reference.
573
referenced_branch = self.open_branch()
574
return referenced_branch.bzrdir.cloning_metadir()
575
if len(response) != 3:
576
raise errors.UnexpectedSmartServerResponse(response)
577
control_name, repo_name, branch_info = response
578
if len(branch_info) != 2:
579
raise errors.UnexpectedSmartServerResponse(response)
580
branch_ref, branch_name = branch_info
582
format = controldir.network_format_registry.get(control_name)
584
raise errors.UnknownFormatError(kind='control', format=control_name)
588
format.repository_format = _mod_repository.network_format_registry.get(
591
raise errors.UnknownFormatError(kind='repository',
593
if branch_ref == 'ref':
594
# XXX: we need possible_transports here to avoid reopening the
595
# connection to the referenced location
596
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
597
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
598
format.set_branch_format(branch_format)
599
elif branch_ref == 'branch':
602
branch_format = branch.network_format_registry.get(
605
raise errors.UnknownFormatError(kind='branch',
607
format.set_branch_format(branch_format)
609
raise errors.UnexpectedSmartServerResponse(response)
612
def create_repository(self, shared=False):
613
# as per meta1 formats - just delegate to the format object which may
615
result = self._format.repository_format.initialize(self, shared)
616
if not isinstance(result, RemoteRepository):
617
return self.open_repository()
621
def destroy_repository(self):
622
"""See BzrDir.destroy_repository"""
623
path = self._path_for_remote_call(self._client)
625
response = self._call('BzrDir.destroy_repository', path)
626
except errors.UnknownSmartMethod:
628
self._real_bzrdir.destroy_repository()
630
if response[0] != 'ok':
631
raise SmartProtocolError('unexpected response code %s' % (response,))
633
def create_branch(self, name=None, repository=None,
634
append_revisions_only=None):
636
name = self._get_selected_branch()
638
raise errors.NoColocatedBranchSupport(self)
639
# as per meta1 formats - just delegate to the format object which may
641
real_branch = self._format.get_branch_format().initialize(self,
642
name=name, repository=repository,
643
append_revisions_only=append_revisions_only)
644
if not isinstance(real_branch, RemoteBranch):
645
if not isinstance(repository, RemoteRepository):
646
raise AssertionError(
647
'need a RemoteRepository to use with RemoteBranch, got %r'
649
result = RemoteBranch(self, repository, real_branch, name=name)
652
# BzrDir.clone_on_transport() uses the result of create_branch but does
653
# not return it to its callers; we save approximately 8% of our round
654
# trips by handing the branch we created back to the first caller to
655
# open_branch rather than probing anew. Long term we need a API in
656
# bzrdir that doesn't discard result objects (like result_branch).
658
self._next_open_branch_result = result
661
def destroy_branch(self, name=None):
662
"""See BzrDir.destroy_branch"""
664
name = self._get_selected_branch()
666
raise errors.NoColocatedBranchSupport(self)
667
path = self._path_for_remote_call(self._client)
673
response = self._call('BzrDir.destroy_branch', path, *args)
674
except errors.UnknownSmartMethod:
676
self._real_bzrdir.destroy_branch(name=name)
677
self._next_open_branch_result = None
679
self._next_open_branch_result = None
680
if response[0] != 'ok':
681
raise SmartProtocolError('unexpected response code %s' % (response,))
683
def create_workingtree(self, revision_id=None, from_branch=None,
684
accelerator_tree=None, hardlink=False):
685
raise errors.NotLocalUrl(self.transport.base)
687
def find_branch_format(self, name=None):
688
"""Find the branch 'format' for this bzrdir.
690
This might be a synthetic object for e.g. RemoteBranch and SVN.
692
b = self.open_branch(name=name)
695
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
696
path = self._path_for_remote_call(self._client)
698
response, handler = self._call_expecting_body(
699
'BzrDir.get_branches', path)
700
except errors.UnknownSmartMethod:
702
return self._real_bzrdir.get_branches()
703
if response[0] != "success":
704
raise errors.UnexpectedSmartServerResponse(response)
705
body = bencode.bdecode(handler.read_body_bytes())
707
for name, value in viewitems(body):
708
ret[name] = self._open_branch(name, value[0], value[1],
709
possible_transports=possible_transports,
710
ignore_fallbacks=ignore_fallbacks)
713
def set_branch_reference(self, target_branch, name=None):
714
"""See BzrDir.set_branch_reference()."""
716
name = self._get_selected_branch()
718
raise errors.NoColocatedBranchSupport(self)
720
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
722
def get_branch_reference(self, name=None):
723
"""See BzrDir.get_branch_reference()."""
725
name = self._get_selected_branch()
727
raise errors.NoColocatedBranchSupport(self)
728
response = self._get_branch_reference()
729
if response[0] == 'ref':
734
def _get_branch_reference(self):
735
path = self._path_for_remote_call(self._client)
736
medium = self._client._medium
738
('BzrDir.open_branchV3', (2, 1)),
739
('BzrDir.open_branchV2', (1, 13)),
740
('BzrDir.open_branch', None),
742
for verb, required_version in candidate_calls:
743
if required_version and medium._is_remote_before(required_version):
746
response = self._call(verb, path)
747
except errors.UnknownSmartMethod:
748
if required_version is None:
750
medium._remember_remote_is_before(required_version)
753
if verb == 'BzrDir.open_branch':
754
if response[0] != 'ok':
755
raise errors.UnexpectedSmartServerResponse(response)
756
if response[1] != '':
757
return ('ref', response[1])
759
return ('branch', '')
760
if response[0] not in ('ref', 'branch'):
761
raise errors.UnexpectedSmartServerResponse(response)
764
def _get_tree_branch(self, name=None):
765
"""See BzrDir._get_tree_branch()."""
766
return None, self.open_branch(name=name)
768
def _open_branch(self, name, kind, location_or_format,
769
ignore_fallbacks=False, possible_transports=None):
771
# a branch reference, use the existing BranchReference logic.
772
format = BranchReferenceFormat()
773
return format.open(self, name=name, _found=True,
774
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
775
possible_transports=possible_transports)
776
branch_format_name = location_or_format
777
if not branch_format_name:
778
branch_format_name = None
779
format = RemoteBranchFormat(network_name=branch_format_name)
780
return RemoteBranch(self, self.find_repository(), format=format,
781
setup_stacking=not ignore_fallbacks, name=name,
782
possible_transports=possible_transports)
784
def open_branch(self, name=None, unsupported=False,
785
ignore_fallbacks=False, possible_transports=None):
787
name = self._get_selected_branch()
789
raise errors.NoColocatedBranchSupport(self)
791
raise NotImplementedError('unsupported flag support not implemented yet.')
792
if self._next_open_branch_result is not None:
793
# See create_branch for details.
794
result = self._next_open_branch_result
795
self._next_open_branch_result = None
797
response = self._get_branch_reference()
798
return self._open_branch(name, response[0], response[1],
799
possible_transports=possible_transports,
800
ignore_fallbacks=ignore_fallbacks)
802
def _open_repo_v1(self, path):
803
verb = 'BzrDir.find_repository'
804
response = self._call(verb, path)
805
if response[0] != 'ok':
806
raise errors.UnexpectedSmartServerResponse(response)
807
# servers that only support the v1 method don't support external
810
repo = self._real_bzrdir.open_repository()
811
response = response + ('no', repo._format.network_name())
812
return response, repo
814
def _open_repo_v2(self, path):
815
verb = 'BzrDir.find_repositoryV2'
816
response = self._call(verb, path)
817
if response[0] != 'ok':
818
raise errors.UnexpectedSmartServerResponse(response)
820
repo = self._real_bzrdir.open_repository()
821
response = response + (repo._format.network_name(),)
822
return response, repo
824
def _open_repo_v3(self, path):
825
verb = 'BzrDir.find_repositoryV3'
826
medium = self._client._medium
827
if medium._is_remote_before((1, 13)):
828
raise errors.UnknownSmartMethod(verb)
830
response = self._call(verb, path)
831
except errors.UnknownSmartMethod:
832
medium._remember_remote_is_before((1, 13))
834
if response[0] != 'ok':
835
raise errors.UnexpectedSmartServerResponse(response)
836
return response, None
838
def open_repository(self):
839
path = self._path_for_remote_call(self._client)
841
for probe in [self._open_repo_v3, self._open_repo_v2,
844
response, real_repo = probe(path)
846
except errors.UnknownSmartMethod:
849
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
850
if response[0] != 'ok':
851
raise errors.UnexpectedSmartServerResponse(response)
852
if len(response) != 6:
853
raise SmartProtocolError('incorrect response length %s' % (response,))
854
if response[1] == '':
855
# repo is at this dir.
856
format = response_tuple_to_repo_format(response[2:])
857
# Used to support creating a real format instance when needed.
858
format._creating_bzrdir = self
859
remote_repo = RemoteRepository(self, format)
860
format._creating_repo = remote_repo
861
if real_repo is not None:
862
remote_repo._set_real_repository(real_repo)
865
raise errors.NoRepositoryPresent(self)
867
def has_workingtree(self):
868
if self._has_working_tree is None:
869
path = self._path_for_remote_call(self._client)
871
response = self._call('BzrDir.has_workingtree', path)
872
except errors.UnknownSmartMethod:
874
self._has_working_tree = self._real_bzrdir.has_workingtree()
876
if response[0] not in ('yes', 'no'):
877
raise SmartProtocolError('unexpected response code %s' % (response,))
878
self._has_working_tree = (response[0] == 'yes')
879
return self._has_working_tree
881
def open_workingtree(self, recommend_upgrade=True):
882
if self.has_workingtree():
883
raise errors.NotLocalUrl(self.root_transport)
885
raise errors.NoWorkingTree(self.root_transport.base)
887
def _path_for_remote_call(self, client):
888
"""Return the path to be used for this bzrdir in a remote call."""
889
return urlutils.split_segment_parameters_raw(
890
client.remote_path_from_transport(self.root_transport))[0]
892
def get_branch_transport(self, branch_format, name=None):
894
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
896
def get_repository_transport(self, repository_format):
898
return self._real_bzrdir.get_repository_transport(repository_format)
900
def get_workingtree_transport(self, workingtree_format):
902
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
904
def can_convert_format(self):
905
"""Upgrading of remote bzrdirs is not supported yet."""
908
def needs_format_conversion(self, format):
909
"""Upgrading of remote bzrdirs is not supported yet."""
912
def _get_config(self):
913
return RemoteBzrDirConfig(self)
915
def _get_config_store(self):
916
return RemoteControlStore(self)
919
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
920
"""Format for repositories accessed over a _SmartClient.
922
Instances of this repository are represented by RemoteRepository
925
The RemoteRepositoryFormat is parameterized during construction
926
to reflect the capabilities of the real, remote format. Specifically
927
the attributes rich_root_data and supports_tree_reference are set
928
on a per instance basis, and are not set (and should not be) at
931
:ivar _custom_format: If set, a specific concrete repository format that
932
will be used when initializing a repository with this
933
RemoteRepositoryFormat.
934
:ivar _creating_repo: If set, the repository object that this
935
RemoteRepositoryFormat was created for: it can be called into
936
to obtain data like the network name.
939
_matchingbzrdir = RemoteBzrDirFormat()
940
supports_full_versioned_files = True
941
supports_leaving_lock = True
944
_mod_repository.RepositoryFormat.__init__(self)
945
self._custom_format = None
946
self._network_name = None
947
self._creating_bzrdir = None
948
self._revision_graph_can_have_wrong_parents = None
949
self._supports_chks = None
950
self._supports_external_lookups = None
951
self._supports_tree_reference = None
952
self._supports_funky_characters = None
953
self._supports_nesting_repositories = None
954
self._rich_root_data = None
957
return "%s(_network_name=%r)" % (self.__class__.__name__,
961
def fast_deltas(self):
963
return self._custom_format.fast_deltas
966
def rich_root_data(self):
967
if self._rich_root_data is None:
969
self._rich_root_data = self._custom_format.rich_root_data
970
return self._rich_root_data
973
def supports_chks(self):
974
if self._supports_chks is None:
976
self._supports_chks = self._custom_format.supports_chks
977
return self._supports_chks
980
def supports_external_lookups(self):
981
if self._supports_external_lookups is None:
983
self._supports_external_lookups = \
984
self._custom_format.supports_external_lookups
985
return self._supports_external_lookups
988
def supports_funky_characters(self):
989
if self._supports_funky_characters is None:
991
self._supports_funky_characters = \
992
self._custom_format.supports_funky_characters
993
return self._supports_funky_characters
996
def supports_nesting_repositories(self):
997
if self._supports_nesting_repositories is None:
999
self._supports_nesting_repositories = \
1000
self._custom_format.supports_nesting_repositories
1001
return self._supports_nesting_repositories
1004
def supports_tree_reference(self):
1005
if self._supports_tree_reference is None:
1007
self._supports_tree_reference = \
1008
self._custom_format.supports_tree_reference
1009
return self._supports_tree_reference
1012
def revision_graph_can_have_wrong_parents(self):
1013
if self._revision_graph_can_have_wrong_parents is None:
1015
self._revision_graph_can_have_wrong_parents = \
1016
self._custom_format.revision_graph_can_have_wrong_parents
1017
return self._revision_graph_can_have_wrong_parents
1019
def _vfs_initialize(self, a_bzrdir, shared):
1020
"""Helper for common code in initialize."""
1021
if self._custom_format:
1022
# Custom format requested
1023
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1024
elif self._creating_bzrdir is not None:
1025
# Use the format that the repository we were created to back
1027
prior_repo = self._creating_bzrdir.open_repository()
1028
prior_repo._ensure_real()
1029
result = prior_repo._real_repository._format.initialize(
1030
a_bzrdir, shared=shared)
1032
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1033
# support remote initialization.
1034
# We delegate to a real object at this point (as RemoteBzrDir
1035
# delegate to the repository format which would lead to infinite
1036
# recursion if we just called a_bzrdir.create_repository.
1037
a_bzrdir._ensure_real()
1038
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1039
if not isinstance(result, RemoteRepository):
1040
return self.open(a_bzrdir)
1044
def initialize(self, a_bzrdir, shared=False):
1045
# Being asked to create on a non RemoteBzrDir:
1046
if not isinstance(a_bzrdir, RemoteBzrDir):
1047
return self._vfs_initialize(a_bzrdir, shared)
1048
medium = a_bzrdir._client._medium
1049
if medium._is_remote_before((1, 13)):
1050
return self._vfs_initialize(a_bzrdir, shared)
1051
# Creating on a remote bzr dir.
1052
# 1) get the network name to use.
1053
if self._custom_format:
1054
network_name = self._custom_format.network_name()
1055
elif self._network_name:
1056
network_name = self._network_name
1058
# Select the current breezy default and ask for that.
1059
reference_bzrdir_format = controldir.format_registry.get('default')()
1060
reference_format = reference_bzrdir_format.repository_format
1061
network_name = reference_format.network_name()
1062
# 2) try direct creation via RPC
1063
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1064
verb = 'BzrDir.create_repository'
1068
shared_str = 'False'
1070
response = a_bzrdir._call(verb, path, network_name, shared_str)
1071
except errors.UnknownSmartMethod:
1072
# Fallback - use vfs methods
1073
medium._remember_remote_is_before((1, 13))
1074
return self._vfs_initialize(a_bzrdir, shared)
1076
# Turn the response into a RemoteRepository object.
1077
format = response_tuple_to_repo_format(response[1:])
1078
# Used to support creating a real format instance when needed.
1079
format._creating_bzrdir = a_bzrdir
1080
remote_repo = RemoteRepository(a_bzrdir, format)
1081
format._creating_repo = remote_repo
1084
def open(self, a_bzrdir):
1085
if not isinstance(a_bzrdir, RemoteBzrDir):
1086
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1087
return a_bzrdir.open_repository()
1089
def _ensure_real(self):
1090
if self._custom_format is None:
1092
self._custom_format = _mod_repository.network_format_registry.get(
1095
raise errors.UnknownFormatError(kind='repository',
1096
format=self._network_name)
1099
def _fetch_order(self):
1101
return self._custom_format._fetch_order
1104
def _fetch_uses_deltas(self):
1106
return self._custom_format._fetch_uses_deltas
1109
def _fetch_reconcile(self):
1111
return self._custom_format._fetch_reconcile
1113
def get_format_description(self):
1115
return 'Remote: ' + self._custom_format.get_format_description()
1117
def __eq__(self, other):
1118
return self.__class__ is other.__class__
1120
def network_name(self):
1121
if self._network_name:
1122
return self._network_name
1123
self._creating_repo._ensure_real()
1124
return self._creating_repo._real_repository._format.network_name()
1127
def pack_compresses(self):
1129
return self._custom_format.pack_compresses
1132
def _serializer(self):
1134
return self._custom_format._serializer
1137
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1138
lock._RelockDebugMixin):
1139
"""Repository accessed over rpc.
1141
For the moment most operations are performed using local transport-backed
1145
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1146
"""Create a RemoteRepository instance.
1148
:param remote_bzrdir: The bzrdir hosting this repository.
1149
:param format: The RemoteFormat object to use.
1150
:param real_repository: If not None, a local implementation of the
1151
repository logic for the repository, usually accessing the data
1153
:param _client: Private testing parameter - override the smart client
1154
to be used by the repository.
1157
self._real_repository = real_repository
1159
self._real_repository = None
1160
self.bzrdir = remote_bzrdir
1162
self._client = remote_bzrdir._client
1164
self._client = _client
1165
self._format = format
1166
self._lock_mode = None
1167
self._lock_token = None
1168
self._write_group_tokens = None
1169
self._lock_count = 0
1170
self._leave_lock = False
1171
# Cache of revision parents; misses are cached during read locks, and
1172
# write locks when no _real_repository has been set.
1173
self._unstacked_provider = graph.CachingParentsProvider(
1174
get_parent_map=self._get_parent_map_rpc)
1175
self._unstacked_provider.disable_cache()
1177
# These depend on the actual remote format, so force them off for
1178
# maximum compatibility. XXX: In future these should depend on the
1179
# remote repository instance, but this is irrelevant until we perform
1180
# reconcile via an RPC call.
1181
self._reconcile_does_inventory_gc = False
1182
self._reconcile_fixes_text_parents = False
1183
self._reconcile_backsup_inventory = False
1184
self.base = self.bzrdir.transport.base
1185
# Additional places to query for data.
1186
self._fallback_repositories = []
1189
def user_transport(self):
1190
return self.bzrdir.user_transport
1193
def control_transport(self):
1194
# XXX: Normally you shouldn't directly get at the remote repository
1195
# transport, but I'm not sure it's worth making this method
1196
# optional -- mbp 2010-04-21
1197
return self.bzrdir.get_repository_transport(None)
1200
return "%s(%s)" % (self.__class__.__name__, self.base)
1204
def abort_write_group(self, suppress_errors=False):
1205
"""Complete a write group on the decorated repository.
1207
Smart methods perform operations in a single step so this API
1208
is not really applicable except as a compatibility thunk
1209
for older plugins that don't use e.g. the CommitBuilder
1212
:param suppress_errors: see Repository.abort_write_group.
1214
if self._real_repository:
1216
return self._real_repository.abort_write_group(
1217
suppress_errors=suppress_errors)
1218
if not self.is_in_write_group():
1220
mutter('(suppressed) not in write group')
1222
raise errors.BzrError("not in write group")
1223
path = self.bzrdir._path_for_remote_call(self._client)
1225
response = self._call('Repository.abort_write_group', path,
1226
self._lock_token, self._write_group_tokens)
1227
except Exception as exc:
1228
self._write_group = None
1229
if not suppress_errors:
1231
mutter('abort_write_group failed')
1232
log_exception_quietly()
1233
note(gettext('bzr: ERROR (ignored): %s'), exc)
1235
if response != ('ok', ):
1236
raise errors.UnexpectedSmartServerResponse(response)
1237
self._write_group_tokens = None
1240
def chk_bytes(self):
1241
"""Decorate the real repository for now.
1243
In the long term a full blown network facility is needed to avoid
1244
creating a real repository object locally.
1247
return self._real_repository.chk_bytes
1249
def commit_write_group(self):
1250
"""Complete a write group on the decorated repository.
1252
Smart methods perform operations in a single step so this API
1253
is not really applicable except as a compatibility thunk
1254
for older plugins that don't use e.g. the CommitBuilder
1257
if self._real_repository:
1259
return self._real_repository.commit_write_group()
1260
if not self.is_in_write_group():
1261
raise errors.BzrError("not in write group")
1262
path = self.bzrdir._path_for_remote_call(self._client)
1263
response = self._call('Repository.commit_write_group', path,
1264
self._lock_token, self._write_group_tokens)
1265
if response != ('ok', ):
1266
raise errors.UnexpectedSmartServerResponse(response)
1267
self._write_group_tokens = None
1268
# Refresh data after writing to the repository.
1271
def resume_write_group(self, tokens):
1272
if self._real_repository:
1273
return self._real_repository.resume_write_group(tokens)
1274
path = self.bzrdir._path_for_remote_call(self._client)
1276
response = self._call('Repository.check_write_group', path,
1277
self._lock_token, tokens)
1278
except errors.UnknownSmartMethod:
1280
return self._real_repository.resume_write_group(tokens)
1281
if response != ('ok', ):
1282
raise errors.UnexpectedSmartServerResponse(response)
1283
self._write_group_tokens = tokens
1285
def suspend_write_group(self):
1286
if self._real_repository:
1287
return self._real_repository.suspend_write_group()
1288
ret = self._write_group_tokens or []
1289
self._write_group_tokens = None
1292
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1294
return self._real_repository.get_missing_parent_inventories(
1295
check_for_missing_texts=check_for_missing_texts)
1297
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1299
return self._real_repository.get_rev_id_for_revno(
1302
def get_rev_id_for_revno(self, revno, known_pair):
1303
"""See Repository.get_rev_id_for_revno."""
1304
path = self.bzrdir._path_for_remote_call(self._client)
1306
if self._client._medium._is_remote_before((1, 17)):
1307
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1308
response = self._call(
1309
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1310
except errors.UnknownSmartMethod:
1311
self._client._medium._remember_remote_is_before((1, 17))
1312
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1313
if response[0] == 'ok':
1314
return True, response[1]
1315
elif response[0] == 'history-incomplete':
1316
known_pair = response[1:3]
1317
for fallback in self._fallback_repositories:
1318
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1323
# Not found in any fallbacks
1324
return False, known_pair
1326
raise errors.UnexpectedSmartServerResponse(response)
1328
def _ensure_real(self):
1329
"""Ensure that there is a _real_repository set.
1331
Used before calls to self._real_repository.
1333
Note that _ensure_real causes many roundtrips to the server which are
1334
not desirable, and prevents the use of smart one-roundtrip RPC's to
1335
perform complex operations (such as accessing parent data, streaming
1336
revisions etc). Adding calls to _ensure_real should only be done when
1337
bringing up new functionality, adding fallbacks for smart methods that
1338
require a fallback path, and never to replace an existing smart method
1339
invocation. If in doubt chat to the bzr network team.
1341
if self._real_repository is None:
1342
if 'hpssvfs' in debug.debug_flags:
1344
warning('VFS Repository access triggered\n%s',
1345
''.join(traceback.format_stack()))
1346
self._unstacked_provider.missing_keys.clear()
1347
self.bzrdir._ensure_real()
1348
self._set_real_repository(
1349
self.bzrdir._real_bzrdir.open_repository())
1351
def _translate_error(self, err, **context):
1352
self.bzrdir._translate_error(err, repository=self, **context)
1354
def find_text_key_references(self):
1355
"""Find the text key references within the repository.
1357
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1358
to whether they were referred to by the inventory of the
1359
revision_id that they contain. The inventory texts from all present
1360
revision ids are assessed to generate this report.
1363
return self._real_repository.find_text_key_references()
1365
def _generate_text_key_index(self):
1366
"""Generate a new text key index for the repository.
1368
This is an expensive function that will take considerable time to run.
1370
:return: A dict mapping (file_id, revision_id) tuples to a list of
1371
parents, also (file_id, revision_id) tuples.
1374
return self._real_repository._generate_text_key_index()
1376
def _get_revision_graph(self, revision_id):
1377
"""Private method for using with old (< 1.2) servers to fallback."""
1378
if revision_id is None:
1380
elif _mod_revision.is_null(revision_id):
1383
path = self.bzrdir._path_for_remote_call(self._client)
1384
response = self._call_expecting_body(
1385
'Repository.get_revision_graph', path, revision_id)
1386
response_tuple, response_handler = response
1387
if response_tuple[0] != 'ok':
1388
raise errors.UnexpectedSmartServerResponse(response_tuple)
1389
coded = response_handler.read_body_bytes()
1391
# no revisions in this repository!
1393
lines = coded.split('\n')
1396
d = tuple(line.split())
1397
revision_graph[d[0]] = d[1:]
1399
return revision_graph
1401
def _get_sink(self):
1402
"""See Repository._get_sink()."""
1403
return RemoteStreamSink(self)
1405
def _get_source(self, to_format):
1406
"""Return a source for streaming from this repository."""
1407
return RemoteStreamSource(self, to_format)
1410
def get_file_graph(self):
1411
return graph.Graph(self.texts)
1414
def has_revision(self, revision_id):
1415
"""True if this repository has a copy of the revision."""
1416
# Copy of breezy.repository.Repository.has_revision
1417
return revision_id in self.has_revisions((revision_id,))
1420
def has_revisions(self, revision_ids):
1421
"""Probe to find out the presence of multiple revisions.
1423
:param revision_ids: An iterable of revision_ids.
1424
:return: A set of the revision_ids that were present.
1426
# Copy of breezy.repository.Repository.has_revisions
1427
parent_map = self.get_parent_map(revision_ids)
1428
result = set(parent_map)
1429
if _mod_revision.NULL_REVISION in revision_ids:
1430
result.add(_mod_revision.NULL_REVISION)
1433
def _has_same_fallbacks(self, other_repo):
1434
"""Returns true if the repositories have the same fallbacks."""
1435
# XXX: copied from Repository; it should be unified into a base class
1436
# <https://bugs.launchpad.net/bzr/+bug/401622>
1437
my_fb = self._fallback_repositories
1438
other_fb = other_repo._fallback_repositories
1439
if len(my_fb) != len(other_fb):
1441
for f, g in zip(my_fb, other_fb):
1442
if not f.has_same_location(g):
1446
def has_same_location(self, other):
1447
# TODO: Move to RepositoryBase and unify with the regular Repository
1448
# one; unfortunately the tests rely on slightly different behaviour at
1449
# present -- mbp 20090710
1450
return (self.__class__ is other.__class__ and
1451
self.bzrdir.transport.base == other.bzrdir.transport.base)
1453
def get_graph(self, other_repository=None):
1454
"""Return the graph for this repository format"""
1455
parents_provider = self._make_parents_provider(other_repository)
1456
return graph.Graph(parents_provider)
1459
def get_known_graph_ancestry(self, revision_ids):
1460
"""Return the known graph for a set of revision ids and their ancestors.
1462
st = static_tuple.StaticTuple
1463
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1464
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1465
return graph.GraphThunkIdsToKeys(known_graph)
1467
def gather_stats(self, revid=None, committers=None):
1468
"""See Repository.gather_stats()."""
1469
path = self.bzrdir._path_for_remote_call(self._client)
1470
# revid can be None to indicate no revisions, not just NULL_REVISION
1471
if revid is None or _mod_revision.is_null(revid):
1475
if committers is None or not committers:
1476
fmt_committers = 'no'
1478
fmt_committers = 'yes'
1479
response_tuple, response_handler = self._call_expecting_body(
1480
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1481
if response_tuple[0] != 'ok':
1482
raise errors.UnexpectedSmartServerResponse(response_tuple)
1484
body = response_handler.read_body_bytes()
1486
for line in body.split('\n'):
1489
key, val_text = line.split(':')
1490
if key in ('revisions', 'size', 'committers'):
1491
result[key] = int(val_text)
1492
elif key in ('firstrev', 'latestrev'):
1493
values = val_text.split(' ')[1:]
1494
result[key] = (float(values[0]), int(values[1]))
1498
def find_branches(self, using=False):
1499
"""See Repository.find_branches()."""
1500
# should be an API call to the server.
1502
return self._real_repository.find_branches(using=using)
1504
def get_physical_lock_status(self):
1505
"""See Repository.get_physical_lock_status()."""
1506
path = self.bzrdir._path_for_remote_call(self._client)
1508
response = self._call('Repository.get_physical_lock_status', path)
1509
except errors.UnknownSmartMethod:
1511
return self._real_repository.get_physical_lock_status()
1512
if response[0] not in ('yes', 'no'):
1513
raise errors.UnexpectedSmartServerResponse(response)
1514
return (response[0] == 'yes')
1516
def is_in_write_group(self):
1517
"""Return True if there is an open write group.
1519
write groups are only applicable locally for the smart server..
1521
if self._write_group_tokens is not None:
1523
if self._real_repository:
1524
return self._real_repository.is_in_write_group()
1526
def is_locked(self):
1527
return self._lock_count >= 1
1529
def is_shared(self):
1530
"""See Repository.is_shared()."""
1531
path = self.bzrdir._path_for_remote_call(self._client)
1532
response = self._call('Repository.is_shared', path)
1533
if response[0] not in ('yes', 'no'):
1534
raise SmartProtocolError('unexpected response code %s' % (response,))
1535
return response[0] == 'yes'
1537
def is_write_locked(self):
1538
return self._lock_mode == 'w'
1540
def _warn_if_deprecated(self, branch=None):
1541
# If we have a real repository, the check will be done there, if we
1542
# don't the check will be done remotely.
1545
def lock_read(self):
1546
"""Lock the repository for read operations.
1548
:return: A breezy.lock.LogicalLockResult.
1550
# wrong eventually - want a local lock cache context
1551
if not self._lock_mode:
1552
self._note_lock('r')
1553
self._lock_mode = 'r'
1554
self._lock_count = 1
1555
self._unstacked_provider.enable_cache(cache_misses=True)
1556
if self._real_repository is not None:
1557
self._real_repository.lock_read()
1558
for repo in self._fallback_repositories:
1561
self._lock_count += 1
1562
return lock.LogicalLockResult(self.unlock)
1564
def _remote_lock_write(self, token):
1565
path = self.bzrdir._path_for_remote_call(self._client)
1568
err_context = {'token': token}
1569
response = self._call('Repository.lock_write', path, token,
1571
if response[0] == 'ok':
1572
ok, token = response
1575
raise errors.UnexpectedSmartServerResponse(response)
1577
def lock_write(self, token=None, _skip_rpc=False):
1578
if not self._lock_mode:
1579
self._note_lock('w')
1581
if self._lock_token is not None:
1582
if token != self._lock_token:
1583
raise errors.TokenMismatch(token, self._lock_token)
1584
self._lock_token = token
1586
self._lock_token = self._remote_lock_write(token)
1587
# if self._lock_token is None, then this is something like packs or
1588
# svn where we don't get to lock the repo, or a weave style repository
1589
# where we cannot lock it over the wire and attempts to do so will
1591
if self._real_repository is not None:
1592
self._real_repository.lock_write(token=self._lock_token)
1593
if token is not None:
1594
self._leave_lock = True
1596
self._leave_lock = False
1597
self._lock_mode = 'w'
1598
self._lock_count = 1
1599
cache_misses = self._real_repository is None
1600
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1601
for repo in self._fallback_repositories:
1602
# Writes don't affect fallback repos
1604
elif self._lock_mode == 'r':
1605
raise errors.ReadOnlyError(self)
1607
self._lock_count += 1
1608
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1610
def leave_lock_in_place(self):
1611
if not self._lock_token:
1612
raise NotImplementedError(self.leave_lock_in_place)
1613
self._leave_lock = True
1615
def dont_leave_lock_in_place(self):
1616
if not self._lock_token:
1617
raise NotImplementedError(self.dont_leave_lock_in_place)
1618
self._leave_lock = False
1620
def _set_real_repository(self, repository):
1621
"""Set the _real_repository for this repository.
1623
:param repository: The repository to fallback to for non-hpss
1624
implemented operations.
1626
if self._real_repository is not None:
1627
# Replacing an already set real repository.
1628
# We cannot do this [currently] if the repository is locked -
1629
# synchronised state might be lost.
1630
if self.is_locked():
1631
raise AssertionError('_real_repository is already set')
1632
if isinstance(repository, RemoteRepository):
1633
raise AssertionError()
1634
self._real_repository = repository
1635
# three code paths happen here:
1636
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1637
# up stacking. In this case self._fallback_repositories is [], and the
1638
# real repo is already setup. Preserve the real repo and
1639
# RemoteRepository.add_fallback_repository will avoid adding
1641
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1642
# ensure_real is triggered from a branch, the real repository to
1643
# set already has a matching list with separate instances, but
1644
# as they are also RemoteRepositories we don't worry about making the
1645
# lists be identical.
1646
# 3) new servers, RemoteRepository.ensure_real is triggered before
1647
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1648
# and need to populate it.
1649
if (self._fallback_repositories and
1650
len(self._real_repository._fallback_repositories) !=
1651
len(self._fallback_repositories)):
1652
if len(self._real_repository._fallback_repositories):
1653
raise AssertionError(
1654
"cannot cleanly remove existing _fallback_repositories")
1655
for fb in self._fallback_repositories:
1656
self._real_repository.add_fallback_repository(fb)
1657
if self._lock_mode == 'w':
1658
# if we are already locked, the real repository must be able to
1659
# acquire the lock with our token.
1660
self._real_repository.lock_write(self._lock_token)
1661
elif self._lock_mode == 'r':
1662
self._real_repository.lock_read()
1663
if self._write_group_tokens is not None:
1664
# if we are already in a write group, resume it
1665
self._real_repository.resume_write_group(self._write_group_tokens)
1666
self._write_group_tokens = None
1668
def start_write_group(self):
1669
"""Start a write group on the decorated repository.
1671
Smart methods perform operations in a single step so this API
1672
is not really applicable except as a compatibility thunk
1673
for older plugins that don't use e.g. the CommitBuilder
1676
if self._real_repository:
1678
return self._real_repository.start_write_group()
1679
if not self.is_write_locked():
1680
raise errors.NotWriteLocked(self)
1681
if self._write_group_tokens is not None:
1682
raise errors.BzrError('already in a write group')
1683
path = self.bzrdir._path_for_remote_call(self._client)
1685
response = self._call('Repository.start_write_group', path,
1687
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1689
return self._real_repository.start_write_group()
1690
if response[0] != 'ok':
1691
raise errors.UnexpectedSmartServerResponse(response)
1692
self._write_group_tokens = response[1]
1694
def _unlock(self, token):
1695
path = self.bzrdir._path_for_remote_call(self._client)
1697
# with no token the remote repository is not persistently locked.
1699
err_context = {'token': token}
1700
response = self._call('Repository.unlock', path, token,
1702
if response == ('ok',):
1705
raise errors.UnexpectedSmartServerResponse(response)
1707
@only_raises(errors.LockNotHeld, errors.LockBroken)
1709
if not self._lock_count:
1710
return lock.cant_unlock_not_held(self)
1711
self._lock_count -= 1
1712
if self._lock_count > 0:
1714
self._unstacked_provider.disable_cache()
1715
old_mode = self._lock_mode
1716
self._lock_mode = None
1718
# The real repository is responsible at present for raising an
1719
# exception if it's in an unfinished write group. However, it
1720
# normally will *not* actually remove the lock from disk - that's
1721
# done by the server on receiving the Repository.unlock call.
1722
# This is just to let the _real_repository stay up to date.
1723
if self._real_repository is not None:
1724
self._real_repository.unlock()
1725
elif self._write_group_tokens is not None:
1726
self.abort_write_group()
1728
# The rpc-level lock should be released even if there was a
1729
# problem releasing the vfs-based lock.
1731
# Only write-locked repositories need to make a remote method
1732
# call to perform the unlock.
1733
old_token = self._lock_token
1734
self._lock_token = None
1735
if not self._leave_lock:
1736
self._unlock(old_token)
1737
# Fallbacks are always 'lock_read()' so we don't pay attention to
1739
for repo in self._fallback_repositories:
1742
def break_lock(self):
1743
# should hand off to the network
1744
path = self.bzrdir._path_for_remote_call(self._client)
1746
response = self._call("Repository.break_lock", path)
1747
except errors.UnknownSmartMethod:
1749
return self._real_repository.break_lock()
1750
if response != ('ok',):
1751
raise errors.UnexpectedSmartServerResponse(response)
1753
def _get_tarball(self, compression):
1754
"""Return a TemporaryFile containing a repository tarball.
1756
Returns None if the server does not support sending tarballs.
1759
path = self.bzrdir._path_for_remote_call(self._client)
1761
response, protocol = self._call_expecting_body(
1762
'Repository.tarball', path, compression)
1763
except errors.UnknownSmartMethod:
1764
protocol.cancel_read_body()
1766
if response[0] == 'ok':
1767
# Extract the tarball and return it
1768
t = tempfile.NamedTemporaryFile()
1769
# TODO: rpc layer should read directly into it...
1770
t.write(protocol.read_body_bytes())
1773
raise errors.UnexpectedSmartServerResponse(response)
1776
def sprout(self, to_bzrdir, revision_id=None):
1777
"""Create a descendent repository for new development.
1779
Unlike clone, this does not copy the settings of the repository.
1781
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1782
dest_repo.fetch(self, revision_id=revision_id)
1785
def _create_sprouting_repo(self, a_bzrdir, shared):
1786
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1787
# use target default format.
1788
dest_repo = a_bzrdir.create_repository()
1790
# Most control formats need the repository to be specifically
1791
# created, but on some old all-in-one formats it's not needed
1793
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1794
except errors.UninitializableFormat:
1795
dest_repo = a_bzrdir.open_repository()
1798
### These methods are just thin shims to the VFS object for now.
1801
def revision_tree(self, revision_id):
1802
revision_id = _mod_revision.ensure_null(revision_id)
1803
if revision_id == _mod_revision.NULL_REVISION:
1804
return InventoryRevisionTree(self,
1805
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1807
return list(self.revision_trees([revision_id]))[0]
1809
def get_serializer_format(self):
1810
path = self.bzrdir._path_for_remote_call(self._client)
1812
response = self._call('VersionedFileRepository.get_serializer_format',
1814
except errors.UnknownSmartMethod:
1816
return self._real_repository.get_serializer_format()
1817
if response[0] != 'ok':
1818
raise errors.UnexpectedSmartServerResponse(response)
1821
def get_commit_builder(self, branch, parents, config, timestamp=None,
1822
timezone=None, committer=None, revprops=None,
1823
revision_id=None, lossy=False):
1824
"""Obtain a CommitBuilder for this repository.
1826
:param branch: Branch to commit to.
1827
:param parents: Revision ids of the parents of the new revision.
1828
:param config: Configuration to use.
1829
:param timestamp: Optional timestamp recorded for commit.
1830
:param timezone: Optional timezone for timestamp.
1831
:param committer: Optional committer to set for commit.
1832
:param revprops: Optional dictionary of revision properties.
1833
:param revision_id: Optional revision id.
1834
:param lossy: Whether to discard data that can not be natively
1835
represented, when pushing to a foreign VCS
1837
if self._fallback_repositories and not self._format.supports_chks:
1838
raise errors.BzrError("Cannot commit directly to a stacked branch"
1839
" in pre-2a formats. See "
1840
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1841
if self._format.rich_root_data:
1842
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1844
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1845
result = commit_builder_kls(self, parents, config,
1846
timestamp, timezone, committer, revprops, revision_id,
1848
self.start_write_group()
1851
def add_fallback_repository(self, repository):
1852
"""Add a repository to use for looking up data not held locally.
1854
:param repository: A repository.
1856
if not self._format.supports_external_lookups:
1857
raise errors.UnstackableRepositoryFormat(
1858
self._format.network_name(), self.base)
1859
# We need to accumulate additional repositories here, to pass them in
1862
# Make the check before we lock: this raises an exception.
1863
self._check_fallback_repository(repository)
1864
if self.is_locked():
1865
# We will call fallback.unlock() when we transition to the unlocked
1866
# state, so always add a lock here. If a caller passes us a locked
1867
# repository, they are responsible for unlocking it later.
1868
repository.lock_read()
1869
self._fallback_repositories.append(repository)
1870
# If self._real_repository was parameterised already (e.g. because a
1871
# _real_branch had its get_stacked_on_url method called), then the
1872
# repository to be added may already be in the _real_repositories list.
1873
if self._real_repository is not None:
1874
fallback_locations = [repo.user_url for repo in
1875
self._real_repository._fallback_repositories]
1876
if repository.user_url not in fallback_locations:
1877
self._real_repository.add_fallback_repository(repository)
1879
def _check_fallback_repository(self, repository):
1880
"""Check that this repository can fallback to repository safely.
1882
Raise an error if not.
1884
:param repository: A repository to fallback to.
1886
return _mod_repository.InterRepository._assert_same_model(
1889
def add_inventory(self, revid, inv, parents):
1891
return self._real_repository.add_inventory(revid, inv, parents)
1893
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1894
parents, basis_inv=None, propagate_caches=False):
1896
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1897
delta, new_revision_id, parents, basis_inv=basis_inv,
1898
propagate_caches=propagate_caches)
1900
def add_revision(self, revision_id, rev, inv=None):
1901
_mod_revision.check_not_reserved_id(revision_id)
1902
key = (revision_id,)
1903
# check inventory present
1904
if not self.inventories.get_parent_map([key]):
1906
raise errors.WeaveRevisionNotPresent(revision_id,
1909
# yes, this is not suitable for adding with ghosts.
1910
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1913
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1914
self._add_revision(rev)
1916
def _add_revision(self, rev):
1917
if self._real_repository is not None:
1918
return self._real_repository._add_revision(rev)
1919
text = self._serializer.write_revision_to_string(rev)
1920
key = (rev.revision_id,)
1921
parents = tuple((parent,) for parent in rev.parent_ids)
1922
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1923
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1924
self._format, self._write_group_tokens)
1927
def get_inventory(self, revision_id):
1928
return list(self.iter_inventories([revision_id]))[0]
1930
def _iter_inventories_rpc(self, revision_ids, ordering):
1931
if ordering is None:
1932
ordering = 'unordered'
1933
path = self.bzrdir._path_for_remote_call(self._client)
1934
body = "\n".join(revision_ids)
1935
response_tuple, response_handler = (
1936
self._call_with_body_bytes_expecting_body(
1937
"VersionedFileRepository.get_inventories",
1938
(path, ordering), body))
1939
if response_tuple[0] != "ok":
1940
raise errors.UnexpectedSmartServerResponse(response_tuple)
1941
deserializer = inventory_delta.InventoryDeltaDeserializer()
1942
byte_stream = response_handler.read_streamed_body()
1943
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1945
# no results whatsoever
1947
src_format, stream = decoded
1948
if src_format.network_name() != self._format.network_name():
1949
raise AssertionError(
1950
"Mismatched RemoteRepository and stream src %r, %r" % (
1951
src_format.network_name(), self._format.network_name()))
1952
# ignore the src format, it's not really relevant
1953
prev_inv = Inventory(root_id=None,
1954
revision_id=_mod_revision.NULL_REVISION)
1955
# there should be just one substream, with inventory deltas
1956
substream_kind, substream = next(stream)
1957
if substream_kind != "inventory-deltas":
1958
raise AssertionError(
1959
"Unexpected stream %r received" % substream_kind)
1960
for record in substream:
1961
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1962
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1963
if parent_id != prev_inv.revision_id:
1964
raise AssertionError("invalid base %r != %r" % (parent_id,
1965
prev_inv.revision_id))
1966
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1967
yield inv, inv.revision_id
1970
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1972
return self._real_repository._iter_inventories(revision_ids, ordering)
1974
def iter_inventories(self, revision_ids, ordering=None):
1975
"""Get many inventories by revision_ids.
1977
This will buffer some or all of the texts used in constructing the
1978
inventories in memory, but will only parse a single inventory at a
1981
:param revision_ids: The expected revision ids of the inventories.
1982
:param ordering: optional ordering, e.g. 'topological'. If not
1983
specified, the order of revision_ids will be preserved (by
1984
buffering if necessary).
1985
:return: An iterator of inventories.
1987
if ((None in revision_ids)
1988
or (_mod_revision.NULL_REVISION in revision_ids)):
1989
raise ValueError('cannot get null revision inventory')
1990
for inv, revid in self._iter_inventories(revision_ids, ordering):
1992
raise errors.NoSuchRevision(self, revid)
1995
def _iter_inventories(self, revision_ids, ordering=None):
1996
if len(revision_ids) == 0:
1998
missing = set(revision_ids)
1999
if ordering is None:
2000
order_as_requested = True
2002
order = list(revision_ids)
2004
next_revid = order.pop()
2006
order_as_requested = False
2007
if ordering != 'unordered' and self._fallback_repositories:
2008
raise ValueError('unsupported ordering %r' % ordering)
2009
iter_inv_fns = [self._iter_inventories_rpc] + [
2010
fallback._iter_inventories for fallback in
2011
self._fallback_repositories]
2013
for iter_inv in iter_inv_fns:
2014
request = [revid for revid in revision_ids if revid in missing]
2015
for inv, revid in iter_inv(request, ordering):
2018
missing.remove(inv.revision_id)
2019
if ordering != 'unordered':
2023
if order_as_requested:
2024
# Yield as many results as we can while preserving order.
2025
while next_revid in invs:
2026
inv = invs.pop(next_revid)
2027
yield inv, inv.revision_id
2029
next_revid = order.pop()
2031
# We still want to fully consume the stream, just
2032
# in case it is not actually finished at this point
2035
except errors.UnknownSmartMethod:
2036
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2040
if order_as_requested:
2041
if next_revid is not None:
2042
yield None, next_revid
2045
yield invs.get(revid), revid
2048
yield None, missing.pop()
2051
def get_revision(self, revision_id):
2052
return self.get_revisions([revision_id])[0]
2054
def get_transaction(self):
2056
return self._real_repository.get_transaction()
2059
def clone(self, a_bzrdir, revision_id=None):
2060
dest_repo = self._create_sprouting_repo(
2061
a_bzrdir, shared=self.is_shared())
2062
self.copy_content_into(dest_repo, revision_id)
2065
def make_working_trees(self):
2066
"""See Repository.make_working_trees"""
2067
path = self.bzrdir._path_for_remote_call(self._client)
2069
response = self._call('Repository.make_working_trees', path)
2070
except errors.UnknownSmartMethod:
2072
return self._real_repository.make_working_trees()
2073
if response[0] not in ('yes', 'no'):
2074
raise SmartProtocolError('unexpected response code %s' % (response,))
2075
return response[0] == 'yes'
2077
def refresh_data(self):
2078
"""Re-read any data needed to synchronise with disk.
2080
This method is intended to be called after another repository instance
2081
(such as one used by a smart server) has inserted data into the
2082
repository. On all repositories this will work outside of write groups.
2083
Some repository formats (pack and newer for breezy native formats)
2084
support refresh_data inside write groups. If called inside a write
2085
group on a repository that does not support refreshing in a write group
2086
IsInWriteGroupError will be raised.
2088
if self._real_repository is not None:
2089
self._real_repository.refresh_data()
2090
# Refresh the parents cache for this object
2091
self._unstacked_provider.disable_cache()
2092
self._unstacked_provider.enable_cache()
2094
def revision_ids_to_search_result(self, result_set):
2095
"""Convert a set of revision ids to a graph SearchResult."""
2096
result_parents = set()
2097
for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
2098
result_parents.update(parents)
2099
included_keys = result_set.intersection(result_parents)
2100
start_keys = result_set.difference(included_keys)
2101
exclude_keys = result_parents.difference(result_set)
2102
result = vf_search.SearchResult(start_keys, exclude_keys,
2103
len(result_set), result_set)
2107
def search_missing_revision_ids(self, other,
2108
find_ghosts=True, revision_ids=None, if_present_ids=None,
2110
"""Return the revision ids that other has that this does not.
2112
These are returned in topological order.
2114
revision_id: only return revision ids included by revision_id.
2116
inter_repo = _mod_repository.InterRepository.get(other, self)
2117
return inter_repo.search_missing_revision_ids(
2118
find_ghosts=find_ghosts, revision_ids=revision_ids,
2119
if_present_ids=if_present_ids, limit=limit)
2121
def fetch(self, source, revision_id=None, find_ghosts=False,
2123
# No base implementation to use as RemoteRepository is not a subclass
2124
# of Repository; so this is a copy of Repository.fetch().
2125
if fetch_spec is not None and revision_id is not None:
2126
raise AssertionError(
2127
"fetch_spec and revision_id are mutually exclusive.")
2128
if self.is_in_write_group():
2129
raise errors.InternalBzrError(
2130
"May not fetch while in a write group.")
2131
# fast path same-url fetch operations
2132
if (self.has_same_location(source)
2133
and fetch_spec is None
2134
and self._has_same_fallbacks(source)):
2135
# check that last_revision is in 'from' and then return a
2137
if (revision_id is not None and
2138
not _mod_revision.is_null(revision_id)):
2139
self.get_revision(revision_id)
2141
# if there is no specific appropriate InterRepository, this will get
2142
# the InterRepository base class, which raises an
2143
# IncompatibleRepositories when asked to fetch.
2144
inter = _mod_repository.InterRepository.get(source, self)
2145
if (fetch_spec is not None and
2146
not getattr(inter, "supports_fetch_spec", False)):
2147
raise errors.UnsupportedOperation(
2148
"fetch_spec not supported for %r" % inter)
2149
return inter.fetch(revision_id=revision_id,
2150
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2152
def create_bundle(self, target, base, fileobj, format=None):
2154
self._real_repository.create_bundle(target, base, fileobj, format)
2156
def fileids_altered_by_revision_ids(self, revision_ids):
2158
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2160
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2162
return self._real_repository._get_versioned_file_checker(
2163
revisions, revision_versions_cache)
2165
def _iter_files_bytes_rpc(self, desired_files, absent):
2166
path = self.bzrdir._path_for_remote_call(self._client)
2169
for (file_id, revid, identifier) in desired_files:
2170
lines.append("%s\0%s" % (
2171
osutils.safe_file_id(file_id),
2172
osutils.safe_revision_id(revid)))
2173
identifiers.append(identifier)
2174
(response_tuple, response_handler) = (
2175
self._call_with_body_bytes_expecting_body(
2176
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2177
if response_tuple != ('ok', ):
2178
response_handler.cancel_read_body()
2179
raise errors.UnexpectedSmartServerResponse(response_tuple)
2180
byte_stream = response_handler.read_streamed_body()
2181
def decompress_stream(start, byte_stream, unused):
2182
decompressor = zlib.decompressobj()
2183
yield decompressor.decompress(start)
2184
while decompressor.unused_data == "":
2186
data = next(byte_stream)
2187
except StopIteration:
2189
yield decompressor.decompress(data)
2190
yield decompressor.flush()
2191
unused.append(decompressor.unused_data)
2194
while not "\n" in unused:
2195
unused += next(byte_stream)
2196
header, rest = unused.split("\n", 1)
2197
args = header.split("\0")
2198
if args[0] == "absent":
2199
absent[identifiers[int(args[3])]] = (args[1], args[2])
2202
elif args[0] == "ok":
2205
raise errors.UnexpectedSmartServerResponse(args)
2207
yield (identifiers[idx],
2208
decompress_stream(rest, byte_stream, unused_chunks))
2209
unused = "".join(unused_chunks)
2211
def iter_files_bytes(self, desired_files):
2212
"""See Repository.iter_file_bytes.
2216
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2217
desired_files, absent):
2218
yield identifier, bytes_iterator
2219
for fallback in self._fallback_repositories:
2222
desired_files = [(key[0], key[1], identifier)
2223
for identifier, key in viewitems(absent)]
2224
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2225
del absent[identifier]
2226
yield identifier, bytes_iterator
2228
# There may be more missing items, but raise an exception
2230
missing_identifier = next(iter(absent))
2231
missing_key = absent[missing_identifier]
2232
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2233
file_id=missing_key[0])
2234
except errors.UnknownSmartMethod:
2236
for (identifier, bytes_iterator) in (
2237
self._real_repository.iter_files_bytes(desired_files)):
2238
yield identifier, bytes_iterator
2240
def get_cached_parent_map(self, revision_ids):
2241
"""See breezy.CachingParentsProvider.get_cached_parent_map"""
2242
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2244
def get_parent_map(self, revision_ids):
2245
"""See breezy.Graph.get_parent_map()."""
2246
return self._make_parents_provider().get_parent_map(revision_ids)
2248
def _get_parent_map_rpc(self, keys):
2249
"""Helper for get_parent_map that performs the RPC."""
2250
medium = self._client._medium
2251
if medium._is_remote_before((1, 2)):
2252
# We already found out that the server can't understand
2253
# Repository.get_parent_map requests, so just fetch the whole
2256
# Note that this reads the whole graph, when only some keys are
2257
# wanted. On this old server there's no way (?) to get them all
2258
# in one go, and the user probably will have seen a warning about
2259
# the server being old anyhow.
2260
rg = self._get_revision_graph(None)
2261
# There is an API discrepancy between get_parent_map and
2262
# get_revision_graph. Specifically, a "key:()" pair in
2263
# get_revision_graph just means a node has no parents. For
2264
# "get_parent_map" it means the node is a ghost. So fix up the
2265
# graph to correct this.
2266
# https://bugs.launchpad.net/bzr/+bug/214894
2267
# There is one other "bug" which is that ghosts in
2268
# get_revision_graph() are not returned at all. But we won't worry
2269
# about that for now.
2270
for node_id, parent_ids in viewitems(rg):
2271
if parent_ids == ():
2272
rg[node_id] = (NULL_REVISION,)
2273
rg[NULL_REVISION] = ()
2278
raise ValueError('get_parent_map(None) is not valid')
2279
if NULL_REVISION in keys:
2280
keys.discard(NULL_REVISION)
2281
found_parents = {NULL_REVISION:()}
2283
return found_parents
2286
# TODO(Needs analysis): We could assume that the keys being requested
2287
# from get_parent_map are in a breadth first search, so typically they
2288
# will all be depth N from some common parent, and we don't have to
2289
# have the server iterate from the root parent, but rather from the
2290
# keys we're searching; and just tell the server the keyspace we
2291
# already have; but this may be more traffic again.
2293
# Transform self._parents_map into a search request recipe.
2294
# TODO: Manage this incrementally to avoid covering the same path
2295
# repeatedly. (The server will have to on each request, but the less
2296
# work done the better).
2298
# Negative caching notes:
2299
# new server sends missing when a request including the revid
2300
# 'include-missing:' is present in the request.
2301
# missing keys are serialised as missing:X, and we then call
2302
# provider.note_missing(X) for-all X
2303
parents_map = self._unstacked_provider.get_cached_map()
2304
if parents_map is None:
2305
# Repository is not locked, so there's no cache.
2307
if _DEFAULT_SEARCH_DEPTH <= 0:
2308
(start_set, stop_keys,
2309
key_count) = vf_search.search_result_from_parent_map(
2310
parents_map, self._unstacked_provider.missing_keys)
2312
(start_set, stop_keys,
2313
key_count) = vf_search.limited_search_result_from_parent_map(
2314
parents_map, self._unstacked_provider.missing_keys,
2315
keys, depth=_DEFAULT_SEARCH_DEPTH)
2316
recipe = ('manual', start_set, stop_keys, key_count)
2317
body = self._serialise_search_recipe(recipe)
2318
path = self.bzrdir._path_for_remote_call(self._client)
2320
if not isinstance(key, str):
2322
"key %r not a plain string" % (key,))
2323
verb = 'Repository.get_parent_map'
2324
args = (path, 'include-missing:') + tuple(keys)
2326
response = self._call_with_body_bytes_expecting_body(
2328
except errors.UnknownSmartMethod:
2329
# Server does not support this method, so get the whole graph.
2330
# Worse, we have to force a disconnection, because the server now
2331
# doesn't realise it has a body on the wire to consume, so the
2332
# only way to recover is to abandon the connection.
2334
'Server is too old for fast get_parent_map, reconnecting. '
2335
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2337
# To avoid having to disconnect repeatedly, we keep track of the
2338
# fact the server doesn't understand remote methods added in 1.2.
2339
medium._remember_remote_is_before((1, 2))
2340
# Recurse just once and we should use the fallback code.
2341
return self._get_parent_map_rpc(keys)
2342
response_tuple, response_handler = response
2343
if response_tuple[0] not in ['ok']:
2344
response_handler.cancel_read_body()
2345
raise errors.UnexpectedSmartServerResponse(response_tuple)
2346
if response_tuple[0] == 'ok':
2347
coded = bz2.decompress(response_handler.read_body_bytes())
2349
# no revisions found
2351
lines = coded.split('\n')
2354
d = tuple(line.split())
2356
revision_graph[d[0]] = d[1:]
2359
if d[0].startswith('missing:'):
2361
self._unstacked_provider.note_missing_key(revid)
2363
# no parents - so give the Graph result
2365
revision_graph[d[0]] = (NULL_REVISION,)
2366
return revision_graph
2369
def get_signature_text(self, revision_id):
2370
path = self.bzrdir._path_for_remote_call(self._client)
2372
response_tuple, response_handler = self._call_expecting_body(
2373
'Repository.get_revision_signature_text', path, revision_id)
2374
except errors.UnknownSmartMethod:
2376
return self._real_repository.get_signature_text(revision_id)
2377
except errors.NoSuchRevision as err:
2378
for fallback in self._fallback_repositories:
2380
return fallback.get_signature_text(revision_id)
2381
except errors.NoSuchRevision:
2385
if response_tuple[0] != 'ok':
2386
raise errors.UnexpectedSmartServerResponse(response_tuple)
2387
return response_handler.read_body_bytes()
2390
def _get_inventory_xml(self, revision_id):
2391
# This call is used by older working tree formats,
2392
# which stored a serialized basis inventory.
2394
return self._real_repository._get_inventory_xml(revision_id)
2397
def reconcile(self, other=None, thorough=False):
2398
from .reconcile import RepoReconciler
2399
path = self.bzrdir._path_for_remote_call(self._client)
2401
response, handler = self._call_expecting_body(
2402
'Repository.reconcile', path, self._lock_token)
2403
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2405
return self._real_repository.reconcile(other=other, thorough=thorough)
2406
if response != ('ok', ):
2407
raise errors.UnexpectedSmartServerResponse(response)
2408
body = handler.read_body_bytes()
2409
result = RepoReconciler(self)
2410
for line in body.split('\n'):
2413
key, val_text = line.split(':')
2414
if key == "garbage_inventories":
2415
result.garbage_inventories = int(val_text)
2416
elif key == "inconsistent_parents":
2417
result.inconsistent_parents = int(val_text)
2419
mutter("unknown reconcile key %r" % key)
2422
def all_revision_ids(self):
2423
path = self.bzrdir._path_for_remote_call(self._client)
2425
response_tuple, response_handler = self._call_expecting_body(
2426
"Repository.all_revision_ids", path)
2427
except errors.UnknownSmartMethod:
2429
return self._real_repository.all_revision_ids()
2430
if response_tuple != ("ok", ):
2431
raise errors.UnexpectedSmartServerResponse(response_tuple)
2432
revids = set(response_handler.read_body_bytes().splitlines())
2433
for fallback in self._fallback_repositories:
2434
revids.update(set(fallback.all_revision_ids()))
2437
def _filtered_revision_trees(self, revision_ids, file_ids):
2438
"""Return Tree for a revision on this branch with only some files.
2440
:param revision_ids: a sequence of revision-ids;
2441
a revision-id may not be None or 'null:'
2442
:param file_ids: if not None, the result is filtered
2443
so that only those file-ids, their parents and their
2444
children are included.
2446
inventories = self.iter_inventories(revision_ids)
2447
for inv in inventories:
2448
# Should we introduce a FilteredRevisionTree class rather
2449
# than pre-filter the inventory here?
2450
filtered_inv = inv.filter(file_ids)
2451
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2454
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2455
medium = self._client._medium
2456
if medium._is_remote_before((1, 2)):
2458
for delta in self._real_repository.get_deltas_for_revisions(
2459
revisions, specific_fileids):
2462
# Get the revision-ids of interest
2463
required_trees = set()
2464
for revision in revisions:
2465
required_trees.add(revision.revision_id)
2466
required_trees.update(revision.parent_ids[:1])
2468
# Get the matching filtered trees. Note that it's more
2469
# efficient to pass filtered trees to changes_from() rather
2470
# than doing the filtering afterwards. changes_from() could
2471
# arguably do the filtering itself but it's path-based, not
2472
# file-id based, so filtering before or afterwards is
2474
if specific_fileids is None:
2475
trees = dict((t.get_revision_id(), t) for
2476
t in self.revision_trees(required_trees))
2478
trees = dict((t.get_revision_id(), t) for
2479
t in self._filtered_revision_trees(required_trees,
2482
# Calculate the deltas
2483
for revision in revisions:
2484
if not revision.parent_ids:
2485
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2487
old_tree = trees[revision.parent_ids[0]]
2488
yield trees[revision.revision_id].changes_from(old_tree)
2491
def get_revision_delta(self, revision_id, specific_fileids=None):
2492
r = self.get_revision(revision_id)
2493
return list(self.get_deltas_for_revisions([r],
2494
specific_fileids=specific_fileids))[0]
2497
def revision_trees(self, revision_ids):
2498
inventories = self.iter_inventories(revision_ids)
2499
for inv in inventories:
2500
yield InventoryRevisionTree(self, inv, inv.revision_id)
2503
def get_revision_reconcile(self, revision_id):
2505
return self._real_repository.get_revision_reconcile(revision_id)
2508
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2510
return self._real_repository.check(revision_ids=revision_ids,
2511
callback_refs=callback_refs, check_repo=check_repo)
2513
def copy_content_into(self, destination, revision_id=None):
2514
"""Make a complete copy of the content in self into destination.
2516
This is a destructive operation! Do not use it on existing
2519
interrepo = _mod_repository.InterRepository.get(self, destination)
2520
return interrepo.copy_content(revision_id)
2522
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2523
# get a tarball of the remote repository, and copy from that into the
2526
# TODO: Maybe a progress bar while streaming the tarball?
2527
note(gettext("Copying repository content as tarball..."))
2528
tar_file = self._get_tarball('bz2')
2529
if tar_file is None:
2531
destination = to_bzrdir.create_repository()
2533
tar = tarfile.open('repository', fileobj=tar_file,
2535
tmpdir = osutils.mkdtemp()
2537
_extract_tar(tar, tmpdir)
2538
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2539
tmp_repo = tmp_bzrdir.open_repository()
2540
tmp_repo.copy_content_into(destination, revision_id)
2542
osutils.rmtree(tmpdir)
2546
# TODO: Suggestion from john: using external tar is much faster than
2547
# python's tarfile library, but it may not work on windows.
2550
def inventories(self):
2551
"""Decorate the real repository for now.
2553
In the long term a full blown network facility is needed to
2554
avoid creating a real repository object locally.
2557
return self._real_repository.inventories
2560
def pack(self, hint=None, clean_obsolete_packs=False):
2561
"""Compress the data within the repository.
2566
body = "".join([l+"\n" for l in hint])
2567
path = self.bzrdir._path_for_remote_call(self._client)
2569
response, handler = self._call_with_body_bytes_expecting_body(
2570
'Repository.pack', (path, self._lock_token,
2571
str(clean_obsolete_packs)), body)
2572
except errors.UnknownSmartMethod:
2574
return self._real_repository.pack(hint=hint,
2575
clean_obsolete_packs=clean_obsolete_packs)
2576
handler.cancel_read_body()
2577
if response != ('ok', ):
2578
raise errors.UnexpectedSmartServerResponse(response)
2581
def revisions(self):
2582
"""Decorate the real repository for now.
2584
In the long term a full blown network facility is needed.
2587
return self._real_repository.revisions
2589
def set_make_working_trees(self, new_value):
2591
new_value_str = "True"
2593
new_value_str = "False"
2594
path = self.bzrdir._path_for_remote_call(self._client)
2596
response = self._call(
2597
'Repository.set_make_working_trees', path, new_value_str)
2598
except errors.UnknownSmartMethod:
2600
self._real_repository.set_make_working_trees(new_value)
2602
if response[0] != 'ok':
2603
raise errors.UnexpectedSmartServerResponse(response)
2606
def signatures(self):
2607
"""Decorate the real repository for now.
2609
In the long term a full blown network facility is needed to avoid
2610
creating a real repository object locally.
2613
return self._real_repository.signatures
2616
def sign_revision(self, revision_id, gpg_strategy):
2617
testament = _mod_testament.Testament.from_revision(self, revision_id)
2618
plaintext = testament.as_short_text()
2619
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2623
"""Decorate the real repository for now.
2625
In the long term a full blown network facility is needed to avoid
2626
creating a real repository object locally.
2629
return self._real_repository.texts
2631
def _iter_revisions_rpc(self, revision_ids):
2632
body = "\n".join(revision_ids)
2633
path = self.bzrdir._path_for_remote_call(self._client)
2634
response_tuple, response_handler = (
2635
self._call_with_body_bytes_expecting_body(
2636
"Repository.iter_revisions", (path, ), body))
2637
if response_tuple[0] != "ok":
2638
raise errors.UnexpectedSmartServerResponse(response_tuple)
2639
serializer_format = response_tuple[1]
2640
serializer = serializer_format_registry.get(serializer_format)
2641
byte_stream = response_handler.read_streamed_body()
2642
decompressor = zlib.decompressobj()
2644
for bytes in byte_stream:
2645
chunks.append(decompressor.decompress(bytes))
2646
if decompressor.unused_data != "":
2647
chunks.append(decompressor.flush())
2648
yield serializer.read_revision_from_string("".join(chunks))
2649
unused = decompressor.unused_data
2650
decompressor = zlib.decompressobj()
2651
chunks = [decompressor.decompress(unused)]
2652
chunks.append(decompressor.flush())
2653
text = "".join(chunks)
2655
yield serializer.read_revision_from_string("".join(chunks))
2658
def get_revisions(self, revision_ids):
2659
if revision_ids is None:
2660
revision_ids = self.all_revision_ids()
2662
for rev_id in revision_ids:
2663
if not rev_id or not isinstance(rev_id, basestring):
2664
raise errors.InvalidRevisionId(
2665
revision_id=rev_id, branch=self)
2667
missing = set(revision_ids)
2669
for rev in self._iter_revisions_rpc(revision_ids):
2670
missing.remove(rev.revision_id)
2671
revs[rev.revision_id] = rev
2672
except errors.UnknownSmartMethod:
2674
return self._real_repository.get_revisions(revision_ids)
2675
for fallback in self._fallback_repositories:
2678
for revid in list(missing):
2679
# XXX JRV 2011-11-20: It would be nice if there was a
2680
# public method on Repository that could be used to query
2681
# for revision objects *without* failing completely if one
2682
# was missing. There is VersionedFileRepository._iter_revisions,
2683
# but unfortunately that's private and not provided by
2684
# all repository implementations.
2686
revs[revid] = fallback.get_revision(revid)
2687
except errors.NoSuchRevision:
2690
missing.remove(revid)
2692
raise errors.NoSuchRevision(self, list(missing)[0])
2693
return [revs[revid] for revid in revision_ids]
2695
def supports_rich_root(self):
2696
return self._format.rich_root_data
2699
def _serializer(self):
2700
return self._format._serializer
2703
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2704
signature = gpg_strategy.sign(plaintext)
2705
self.add_signature_text(revision_id, signature)
2707
def add_signature_text(self, revision_id, signature):
2708
if self._real_repository:
2709
# If there is a real repository the write group will
2710
# be in the real repository as well, so use that:
2712
return self._real_repository.add_signature_text(
2713
revision_id, signature)
2714
path = self.bzrdir._path_for_remote_call(self._client)
2715
response, handler = self._call_with_body_bytes_expecting_body(
2716
'Repository.add_signature_text', (path, self._lock_token,
2717
revision_id) + tuple(self._write_group_tokens), signature)
2718
handler.cancel_read_body()
2720
if response[0] != 'ok':
2721
raise errors.UnexpectedSmartServerResponse(response)
2722
self._write_group_tokens = response[1:]
2724
def has_signature_for_revision_id(self, revision_id):
2725
path = self.bzrdir._path_for_remote_call(self._client)
2727
response = self._call('Repository.has_signature_for_revision_id',
2729
except errors.UnknownSmartMethod:
2731
return self._real_repository.has_signature_for_revision_id(
2733
if response[0] not in ('yes', 'no'):
2734
raise SmartProtocolError('unexpected response code %s' % (response,))
2735
if response[0] == 'yes':
2737
for fallback in self._fallback_repositories:
2738
if fallback.has_signature_for_revision_id(revision_id):
2743
def verify_revision_signature(self, revision_id, gpg_strategy):
2744
if not self.has_signature_for_revision_id(revision_id):
2745
return gpg.SIGNATURE_NOT_SIGNED, None
2746
signature = self.get_signature_text(revision_id)
2748
testament = _mod_testament.Testament.from_revision(self, revision_id)
2749
plaintext = testament.as_short_text()
2751
return gpg_strategy.verify(signature, plaintext)
2753
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2755
return self._real_repository.item_keys_introduced_by(revision_ids,
2756
_files_pb=_files_pb)
2758
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2760
return self._real_repository._find_inconsistent_revision_parents(
2763
def _check_for_inconsistent_revision_parents(self):
2765
return self._real_repository._check_for_inconsistent_revision_parents()
2767
def _make_parents_provider(self, other=None):
2768
providers = [self._unstacked_provider]
2769
if other is not None:
2770
providers.insert(0, other)
2771
return graph.StackedParentsProvider(_LazyListJoin(
2772
providers, self._fallback_repositories))
2774
def _serialise_search_recipe(self, recipe):
2775
"""Serialise a graph search recipe.
2777
:param recipe: A search recipe (start, stop, count).
2778
:return: Serialised bytes.
2780
start_keys = ' '.join(recipe[1])
2781
stop_keys = ' '.join(recipe[2])
2782
count = str(recipe[3])
2783
return '\n'.join((start_keys, stop_keys, count))
2785
def _serialise_search_result(self, search_result):
2786
parts = search_result.get_network_struct()
2787
return '\n'.join(parts)
2790
path = self.bzrdir._path_for_remote_call(self._client)
2792
response = self._call('PackRepository.autopack', path)
2793
except errors.UnknownSmartMethod:
2795
self._real_repository._pack_collection.autopack()
2798
if response[0] != 'ok':
2799
raise errors.UnexpectedSmartServerResponse(response)
2802
class RemoteStreamSink(vf_repository.StreamSink):
2804
def _insert_real(self, stream, src_format, resume_tokens):
2805
self.target_repo._ensure_real()
2806
sink = self.target_repo._real_repository._get_sink()
2807
result = sink.insert_stream(stream, src_format, resume_tokens)
2809
self.target_repo.autopack()
2812
def insert_stream(self, stream, src_format, resume_tokens):
2813
target = self.target_repo
2814
target._unstacked_provider.missing_keys.clear()
2815
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2816
if target._lock_token:
2817
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2818
lock_args = (target._lock_token or '',)
2820
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2822
client = target._client
2823
medium = client._medium
2824
path = target.bzrdir._path_for_remote_call(client)
2825
# Probe for the verb to use with an empty stream before sending the
2826
# real stream to it. We do this both to avoid the risk of sending a
2827
# large request that is then rejected, and because we don't want to
2828
# implement a way to buffer, rewind, or restart the stream.
2830
for verb, required_version in candidate_calls:
2831
if medium._is_remote_before(required_version):
2834
# We've already done the probing (and set _is_remote_before) on
2835
# a previous insert.
2838
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2840
response = client.call_with_body_stream(
2841
(verb, path, '') + lock_args, byte_stream)
2842
except errors.UnknownSmartMethod:
2843
medium._remember_remote_is_before(required_version)
2849
return self._insert_real(stream, src_format, resume_tokens)
2850
self._last_inv_record = None
2851
self._last_substream = None
2852
if required_version < (1, 19):
2853
# Remote side doesn't support inventory deltas. Wrap the stream to
2854
# make sure we don't send any. If the stream contains inventory
2855
# deltas we'll interrupt the smart insert_stream request and
2857
stream = self._stop_stream_if_inventory_delta(stream)
2858
byte_stream = smart_repo._stream_to_byte_stream(
2860
resume_tokens = ' '.join(resume_tokens)
2861
response = client.call_with_body_stream(
2862
(verb, path, resume_tokens) + lock_args, byte_stream)
2863
if response[0][0] not in ('ok', 'missing-basis'):
2864
raise errors.UnexpectedSmartServerResponse(response)
2865
if self._last_substream is not None:
2866
# The stream included an inventory-delta record, but the remote
2867
# side isn't new enough to support them. So we need to send the
2868
# rest of the stream via VFS.
2869
self.target_repo.refresh_data()
2870
return self._resume_stream_with_vfs(response, src_format)
2871
if response[0][0] == 'missing-basis':
2872
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2873
resume_tokens = tokens
2874
return resume_tokens, set(missing_keys)
2876
self.target_repo.refresh_data()
2879
def _resume_stream_with_vfs(self, response, src_format):
2880
"""Resume sending a stream via VFS, first resending the record and
2881
substream that couldn't be sent via an insert_stream verb.
2883
if response[0][0] == 'missing-basis':
2884
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2885
# Ignore missing_keys, we haven't finished inserting yet
2888
def resume_substream():
2889
# Yield the substream that was interrupted.
2890
for record in self._last_substream:
2892
self._last_substream = None
2893
def resume_stream():
2894
# Finish sending the interrupted substream
2895
yield ('inventory-deltas', resume_substream())
2896
# Then simply continue sending the rest of the stream.
2897
for substream_kind, substream in self._last_stream:
2898
yield substream_kind, substream
2899
return self._insert_real(resume_stream(), src_format, tokens)
2901
def _stop_stream_if_inventory_delta(self, stream):
2902
"""Normally this just lets the original stream pass-through unchanged.
2904
However if any 'inventory-deltas' substream occurs it will stop
2905
streaming, and store the interrupted substream and stream in
2906
self._last_substream and self._last_stream so that the stream can be
2907
resumed by _resume_stream_with_vfs.
2910
stream_iter = iter(stream)
2911
for substream_kind, substream in stream_iter:
2912
if substream_kind == 'inventory-deltas':
2913
self._last_substream = substream
2914
self._last_stream = stream_iter
2917
yield substream_kind, substream
2920
class RemoteStreamSource(vf_repository.StreamSource):
2921
"""Stream data from a remote server."""
2923
def get_stream(self, search):
2924
if (self.from_repository._fallback_repositories and
2925
self.to_format._fetch_order == 'topological'):
2926
return self._real_stream(self.from_repository, search)
2929
repos = [self.from_repository]
2935
repos.extend(repo._fallback_repositories)
2936
sources.append(repo)
2937
return self.missing_parents_chain(search, sources)
2939
def get_stream_for_missing_keys(self, missing_keys):
2940
self.from_repository._ensure_real()
2941
real_repo = self.from_repository._real_repository
2942
real_source = real_repo._get_source(self.to_format)
2943
return real_source.get_stream_for_missing_keys(missing_keys)
2945
def _real_stream(self, repo, search):
2946
"""Get a stream for search from repo.
2948
This never called RemoteStreamSource.get_stream, and is a helper
2949
for RemoteStreamSource._get_stream to allow getting a stream
2950
reliably whether fallback back because of old servers or trying
2951
to stream from a non-RemoteRepository (which the stacked support
2954
source = repo._get_source(self.to_format)
2955
if isinstance(source, RemoteStreamSource):
2957
source = repo._real_repository._get_source(self.to_format)
2958
return source.get_stream(search)
2960
def _get_stream(self, repo, search):
2961
"""Core worker to get a stream from repo for search.
2963
This is used by both get_stream and the stacking support logic. It
2964
deliberately gets a stream for repo which does not need to be
2965
self.from_repository. In the event that repo is not Remote, or
2966
cannot do a smart stream, a fallback is made to the generic
2967
repository._get_stream() interface, via self._real_stream.
2969
In the event of stacking, streams from _get_stream will not
2970
contain all the data for search - this is normal (see get_stream).
2972
:param repo: A repository.
2973
:param search: A search.
2975
# Fallbacks may be non-smart
2976
if not isinstance(repo, RemoteRepository):
2977
return self._real_stream(repo, search)
2978
client = repo._client
2979
medium = client._medium
2980
path = repo.bzrdir._path_for_remote_call(client)
2981
search_bytes = repo._serialise_search_result(search)
2982
args = (path, self.to_format.network_name())
2984
('Repository.get_stream_1.19', (1, 19)),
2985
('Repository.get_stream', (1, 13))]
2988
for verb, version in candidate_verbs:
2989
if medium._is_remote_before(version):
2992
response = repo._call_with_body_bytes_expecting_body(
2993
verb, args, search_bytes)
2994
except errors.UnknownSmartMethod:
2995
medium._remember_remote_is_before(version)
2996
except errors.UnknownErrorFromSmartServer as e:
2997
if isinstance(search, vf_search.EverythingResult):
2998
error_verb = e.error_from_smart_server.error_verb
2999
if error_verb == 'BadSearch':
3000
# Pre-2.4 servers don't support this sort of search.
3001
# XXX: perhaps falling back to VFS on BadSearch is a
3002
# good idea in general? It might provide a little bit
3003
# of protection against client-side bugs.
3004
medium._remember_remote_is_before((2, 4))
3008
response_tuple, response_handler = response
3012
return self._real_stream(repo, search)
3013
if response_tuple[0] != 'ok':
3014
raise errors.UnexpectedSmartServerResponse(response_tuple)
3015
byte_stream = response_handler.read_streamed_body()
3016
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3017
self._record_counter)
3018
if src_format.network_name() != repo._format.network_name():
3019
raise AssertionError(
3020
"Mismatched RemoteRepository and stream src %r, %r" % (
3021
src_format.network_name(), repo._format.network_name()))
3024
def missing_parents_chain(self, search, sources):
3025
"""Chain multiple streams together to handle stacking.
3027
:param search: The overall search to satisfy with streams.
3028
:param sources: A list of Repository objects to query.
3030
self.from_serialiser = self.from_repository._format._serializer
3031
self.seen_revs = set()
3032
self.referenced_revs = set()
3033
# If there are heads in the search, or the key count is > 0, we are not
3035
while not search.is_empty() and len(sources) > 1:
3036
source = sources.pop(0)
3037
stream = self._get_stream(source, search)
3038
for kind, substream in stream:
3039
if kind != 'revisions':
3040
yield kind, substream
3042
yield kind, self.missing_parents_rev_handler(substream)
3043
search = search.refine(self.seen_revs, self.referenced_revs)
3044
self.seen_revs = set()
3045
self.referenced_revs = set()
3046
if not search.is_empty():
3047
for kind, stream in self._get_stream(sources[0], search):
3050
def missing_parents_rev_handler(self, substream):
3051
for content in substream:
3052
revision_bytes = content.get_bytes_as('fulltext')
3053
revision = self.from_serialiser.read_revision_from_string(
3055
self.seen_revs.add(content.key[-1])
3056
self.referenced_revs.update(revision.parent_ids)
3060
class RemoteBranchLockableFiles(LockableFiles):
3061
"""A 'LockableFiles' implementation that talks to a smart server.
3063
This is not a public interface class.
3066
def __init__(self, bzrdir, _client):
3067
self.bzrdir = bzrdir
3068
self._client = _client
3069
self._need_find_modes = True
3070
LockableFiles.__init__(
3071
self, bzrdir.get_branch_transport(None),
3072
'lock', lockdir.LockDir)
3074
def _find_modes(self):
3075
# RemoteBranches don't let the client set the mode of control files.
3076
self._dir_mode = None
3077
self._file_mode = None
3080
class RemoteBranchFormat(branch.BranchFormat):
3082
def __init__(self, network_name=None):
3083
super(RemoteBranchFormat, self).__init__()
3084
self._matchingbzrdir = RemoteBzrDirFormat()
3085
self._matchingbzrdir.set_branch_format(self)
3086
self._custom_format = None
3087
self._network_name = network_name
3089
def __eq__(self, other):
3090
return (isinstance(other, RemoteBranchFormat) and
3091
self.__dict__ == other.__dict__)
3093
def _ensure_real(self):
3094
if self._custom_format is None:
3096
self._custom_format = branch.network_format_registry.get(
3099
raise errors.UnknownFormatError(kind='branch',
3100
format=self._network_name)
3102
def get_format_description(self):
3104
return 'Remote: ' + self._custom_format.get_format_description()
3106
def network_name(self):
3107
return self._network_name
3109
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3110
return a_bzrdir.open_branch(name=name,
3111
ignore_fallbacks=ignore_fallbacks)
3113
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only,
3115
# Initialisation when using a local bzrdir object, or a non-vfs init
3116
# method is not available on the server.
3117
# self._custom_format is always set - the start of initialize ensures
3119
if isinstance(a_bzrdir, RemoteBzrDir):
3120
a_bzrdir._ensure_real()
3121
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3122
name=name, append_revisions_only=append_revisions_only,
3123
repository=repository)
3125
# We assume the bzrdir is parameterised; it may not be.
3126
result = self._custom_format.initialize(a_bzrdir, name=name,
3127
append_revisions_only=append_revisions_only,
3128
repository=repository)
3129
if (isinstance(a_bzrdir, RemoteBzrDir) and
3130
not isinstance(result, RemoteBranch)):
3131
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3135
def initialize(self, a_bzrdir, name=None, repository=None,
3136
append_revisions_only=None):
3138
name = a_bzrdir._get_selected_branch()
3139
# 1) get the network name to use.
3140
if self._custom_format:
3141
network_name = self._custom_format.network_name()
3143
# Select the current breezy default and ask for that.
3144
reference_bzrdir_format = controldir.format_registry.get('default')()
3145
reference_format = reference_bzrdir_format.get_branch_format()
3146
self._custom_format = reference_format
3147
network_name = reference_format.network_name()
3148
# Being asked to create on a non RemoteBzrDir:
3149
if not isinstance(a_bzrdir, RemoteBzrDir):
3150
return self._vfs_initialize(a_bzrdir, name=name,
3151
append_revisions_only=append_revisions_only,
3152
repository=repository)
3153
medium = a_bzrdir._client._medium
3154
if medium._is_remote_before((1, 13)):
3155
return self._vfs_initialize(a_bzrdir, name=name,
3156
append_revisions_only=append_revisions_only,
3157
repository=repository)
3158
# Creating on a remote bzr dir.
3159
# 2) try direct creation via RPC
3160
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3162
# XXX JRV20100304: Support creating colocated branches
3163
raise errors.NoColocatedBranchSupport(self)
3164
verb = 'BzrDir.create_branch'
3166
response = a_bzrdir._call(verb, path, network_name)
3167
except errors.UnknownSmartMethod:
3168
# Fallback - use vfs methods
3169
medium._remember_remote_is_before((1, 13))
3170
return self._vfs_initialize(a_bzrdir, name=name,
3171
append_revisions_only=append_revisions_only,
3172
repository=repository)
3173
if response[0] != 'ok':
3174
raise errors.UnexpectedSmartServerResponse(response)
3175
# Turn the response into a RemoteRepository object.
3176
format = RemoteBranchFormat(network_name=response[1])
3177
repo_format = response_tuple_to_repo_format(response[3:])
3178
repo_path = response[2]
3179
if repository is not None:
3180
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3181
url_diff = urlutils.relative_url(repository.user_url,
3184
raise AssertionError(
3185
'repository.user_url %r does not match URL from server '
3186
'response (%r + %r)'
3187
% (repository.user_url, a_bzrdir.user_url, repo_path))
3188
remote_repo = repository
3191
repo_bzrdir = a_bzrdir
3193
repo_bzrdir = RemoteBzrDir(
3194
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3196
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3197
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3198
format=format, setup_stacking=False, name=name)
3199
if append_revisions_only:
3200
remote_branch.set_append_revisions_only(append_revisions_only)
3201
# XXX: We know this is a new branch, so it must have revno 0, revid
3202
# NULL_REVISION. Creating the branch locked would make this be unable
3203
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3204
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3205
return remote_branch
3207
def make_tags(self, branch):
3209
return self._custom_format.make_tags(branch)
3211
def supports_tags(self):
3212
# Remote branches might support tags, but we won't know until we
3213
# access the real remote branch.
3215
return self._custom_format.supports_tags()
3217
def supports_stacking(self):
3219
return self._custom_format.supports_stacking()
3221
def supports_set_append_revisions_only(self):
3223
return self._custom_format.supports_set_append_revisions_only()
3225
def _use_default_local_heads_to_fetch(self):
3226
# If the branch format is a metadir format *and* its heads_to_fetch
3227
# implementation is not overridden vs the base class, we can use the
3228
# base class logic rather than use the heads_to_fetch RPC. This is
3229
# usually cheaper in terms of net round trips, as the last-revision and
3230
# tags info fetched is cached and would be fetched anyway.
3232
if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3233
branch_class = self._custom_format._branch_class()
3234
heads_to_fetch_impl = branch_class.heads_to_fetch.__func__
3235
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.__func__:
3240
class RemoteBranchStore(_mod_config.IniFileStore):
3241
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3243
Note that this is specific to bzr-based formats.
3246
def __init__(self, branch):
3247
super(RemoteBranchStore, self).__init__()
3248
self.branch = branch
3250
self._real_store = None
3252
def external_url(self):
3253
return urlutils.join(self.branch.user_url, 'branch.conf')
3255
def _load_content(self):
3256
path = self.branch._remote_path()
3258
response, handler = self.branch._call_expecting_body(
3259
'Branch.get_config_file', path)
3260
except errors.UnknownSmartMethod:
3262
return self._real_store._load_content()
3263
if len(response) and response[0] != 'ok':
3264
raise errors.UnexpectedSmartServerResponse(response)
3265
return handler.read_body_bytes()
3267
def _save_content(self, content):
3268
path = self.branch._remote_path()
3270
response, handler = self.branch._call_with_body_bytes_expecting_body(
3271
'Branch.put_config_file', (path,
3272
self.branch._lock_token, self.branch._repo_lock_token),
3274
except errors.UnknownSmartMethod:
3276
return self._real_store._save_content(content)
3277
handler.cancel_read_body()
3278
if response != ('ok', ):
3279
raise errors.UnexpectedSmartServerResponse(response)
3281
def _ensure_real(self):
3282
self.branch._ensure_real()
3283
if self._real_store is None:
3284
self._real_store = _mod_config.BranchStore(self.branch)
3287
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3288
"""Branch stored on a server accessed by HPSS RPC.
3290
At the moment most operations are mapped down to simple file operations.
3293
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3294
_client=None, format=None, setup_stacking=True, name=None,
3295
possible_transports=None):
3296
"""Create a RemoteBranch instance.
3298
:param real_branch: An optional local implementation of the branch
3299
format, usually accessing the data via the VFS.
3300
:param _client: Private parameter for testing.
3301
:param format: A RemoteBranchFormat object, None to create one
3302
automatically. If supplied it should have a network_name already
3304
:param setup_stacking: If True make an RPC call to determine the
3305
stacked (or not) status of the branch. If False assume the branch
3307
:param name: Colocated branch name
3309
# We intentionally don't call the parent class's __init__, because it
3310
# will try to assign to self.tags, which is a property in this subclass.
3311
# And the parent's __init__ doesn't do much anyway.
3312
self.bzrdir = remote_bzrdir
3314
if _client is not None:
3315
self._client = _client
3317
self._client = remote_bzrdir._client
3318
self.repository = remote_repository
3319
if real_branch is not None:
3320
self._real_branch = real_branch
3321
# Give the remote repository the matching real repo.
3322
real_repo = self._real_branch.repository
3323
if isinstance(real_repo, RemoteRepository):
3324
real_repo._ensure_real()
3325
real_repo = real_repo._real_repository
3326
self.repository._set_real_repository(real_repo)
3327
# Give the branch the remote repository to let fast-pathing happen.
3328
self._real_branch.repository = self.repository
3330
self._real_branch = None
3331
# Fill out expected attributes of branch for breezy API users.
3332
self._clear_cached_state()
3333
# TODO: deprecate self.base in favor of user_url
3334
self.base = self.bzrdir.user_url
3336
self._control_files = None
3337
self._lock_mode = None
3338
self._lock_token = None
3339
self._repo_lock_token = None
3340
self._lock_count = 0
3341
self._leave_lock = False
3342
self.conf_store = None
3343
# Setup a format: note that we cannot call _ensure_real until all the
3344
# attributes above are set: This code cannot be moved higher up in this
3347
self._format = RemoteBranchFormat()
3348
if real_branch is not None:
3349
self._format._network_name = \
3350
self._real_branch._format.network_name()
3352
self._format = format
3353
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3354
# branch.open_branch method.
3355
self._real_ignore_fallbacks = not setup_stacking
3356
if not self._format._network_name:
3357
# Did not get from open_branchV2 - old server.
3359
self._format._network_name = \
3360
self._real_branch._format.network_name()
3361
self.tags = self._format.make_tags(self)
3362
# The base class init is not called, so we duplicate this:
3363
hooks = branch.Branch.hooks['open']
3366
self._is_stacked = False
3368
self._setup_stacking(possible_transports)
3370
def _setup_stacking(self, possible_transports):
3371
# configure stacking into the remote repository, by reading it from
3374
fallback_url = self.get_stacked_on_url()
3375
except (errors.NotStacked, errors.UnstackableBranchFormat,
3376
errors.UnstackableRepositoryFormat) as e:
3378
self._is_stacked = True
3379
if possible_transports is None:
3380
possible_transports = []
3382
possible_transports = list(possible_transports)
3383
possible_transports.append(self.bzrdir.root_transport)
3384
self._activate_fallback_location(fallback_url,
3385
possible_transports=possible_transports)
3387
def _get_config(self):
3388
return RemoteBranchConfig(self)
3390
def _get_config_store(self):
3391
if self.conf_store is None:
3392
self.conf_store = RemoteBranchStore(self)
3393
return self.conf_store
3395
def store_uncommitted(self, creator):
3397
return self._real_branch.store_uncommitted(creator)
3399
def get_unshelver(self, tree):
3401
return self._real_branch.get_unshelver(tree)
3403
def _get_real_transport(self):
3404
# if we try vfs access, return the real branch's vfs transport
3406
return self._real_branch._transport
3408
_transport = property(_get_real_transport)
3411
return "%s(%s)" % (self.__class__.__name__, self.base)
3415
def _ensure_real(self):
3416
"""Ensure that there is a _real_branch set.
3418
Used before calls to self._real_branch.
3420
if self._real_branch is None:
3421
if not vfs.vfs_enabled():
3422
raise AssertionError('smart server vfs must be enabled '
3423
'to use vfs implementation')
3424
self.bzrdir._ensure_real()
3425
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3426
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3427
# The remote branch and the real branch shares the same store. If
3428
# we don't, there will always be cases where one of the stores
3429
# doesn't see an update made on the other.
3430
self._real_branch.conf_store = self.conf_store
3431
if self.repository._real_repository is None:
3432
# Give the remote repository the matching real repo.
3433
real_repo = self._real_branch.repository
3434
if isinstance(real_repo, RemoteRepository):
3435
real_repo._ensure_real()
3436
real_repo = real_repo._real_repository
3437
self.repository._set_real_repository(real_repo)
3438
# Give the real branch the remote repository to let fast-pathing
3440
self._real_branch.repository = self.repository
3441
if self._lock_mode == 'r':
3442
self._real_branch.lock_read()
3443
elif self._lock_mode == 'w':
3444
self._real_branch.lock_write(token=self._lock_token)
3446
def _translate_error(self, err, **context):
3447
self.repository._translate_error(err, branch=self, **context)
3449
def _clear_cached_state(self):
3450
super(RemoteBranch, self)._clear_cached_state()
3451
if self._real_branch is not None:
3452
self._real_branch._clear_cached_state()
3454
def _clear_cached_state_of_remote_branch_only(self):
3455
"""Like _clear_cached_state, but doesn't clear the cache of
3458
This is useful when falling back to calling a method of
3459
self._real_branch that changes state. In that case the underlying
3460
branch changes, so we need to invalidate this RemoteBranch's cache of
3461
it. However, there's no need to invalidate the _real_branch's cache
3462
too, in fact doing so might harm performance.
3464
super(RemoteBranch, self)._clear_cached_state()
3467
def control_files(self):
3468
# Defer actually creating RemoteBranchLockableFiles until its needed,
3469
# because it triggers an _ensure_real that we otherwise might not need.
3470
if self._control_files is None:
3471
self._control_files = RemoteBranchLockableFiles(
3472
self.bzrdir, self._client)
3473
return self._control_files
3475
def get_physical_lock_status(self):
3476
"""See Branch.get_physical_lock_status()."""
3478
response = self._client.call('Branch.get_physical_lock_status',
3479
self._remote_path())
3480
except errors.UnknownSmartMethod:
3482
return self._real_branch.get_physical_lock_status()
3483
if response[0] not in ('yes', 'no'):
3484
raise errors.UnexpectedSmartServerResponse(response)
3485
return (response[0] == 'yes')
3487
def get_stacked_on_url(self):
3488
"""Get the URL this branch is stacked against.
3490
:raises NotStacked: If the branch is not stacked.
3491
:raises UnstackableBranchFormat: If the branch does not support
3493
:raises UnstackableRepositoryFormat: If the repository does not support
3497
# there may not be a repository yet, so we can't use
3498
# self._translate_error, so we can't use self._call either.
3499
response = self._client.call('Branch.get_stacked_on_url',
3500
self._remote_path())
3501
except errors.ErrorFromSmartServer as err:
3502
# there may not be a repository yet, so we can't call through
3503
# its _translate_error
3504
_translate_error(err, branch=self)
3505
except errors.UnknownSmartMethod as err:
3507
return self._real_branch.get_stacked_on_url()
3508
if response[0] != 'ok':
3509
raise errors.UnexpectedSmartServerResponse(response)
3512
def set_stacked_on_url(self, url):
3513
branch.Branch.set_stacked_on_url(self, url)
3514
# We need the stacked_on_url to be visible both locally (to not query
3515
# it repeatedly) and remotely (so smart verbs can get it server side)
3516
# Without the following line,
3517
# breezy.tests.per_branch.test_create_clone.TestCreateClone
3518
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3519
# fails for remote branches -- vila 2012-01-04
3520
self.conf_store.save_changes()
3522
self._is_stacked = False
3524
self._is_stacked = True
3526
def _vfs_get_tags_bytes(self):
3528
return self._real_branch._get_tags_bytes()
3531
def _get_tags_bytes(self):
3532
if self._tags_bytes is None:
3533
self._tags_bytes = self._get_tags_bytes_via_hpss()
3534
return self._tags_bytes
3536
def _get_tags_bytes_via_hpss(self):
3537
medium = self._client._medium
3538
if medium._is_remote_before((1, 13)):
3539
return self._vfs_get_tags_bytes()
3541
response = self._call('Branch.get_tags_bytes', self._remote_path())
3542
except errors.UnknownSmartMethod:
3543
medium._remember_remote_is_before((1, 13))
3544
return self._vfs_get_tags_bytes()
3547
def _vfs_set_tags_bytes(self, bytes):
3549
return self._real_branch._set_tags_bytes(bytes)
3551
def _set_tags_bytes(self, bytes):
3552
if self.is_locked():
3553
self._tags_bytes = bytes
3554
medium = self._client._medium
3555
if medium._is_remote_before((1, 18)):
3556
self._vfs_set_tags_bytes(bytes)
3560
self._remote_path(), self._lock_token, self._repo_lock_token)
3561
response = self._call_with_body_bytes(
3562
'Branch.set_tags_bytes', args, bytes)
3563
except errors.UnknownSmartMethod:
3564
medium._remember_remote_is_before((1, 18))
3565
self._vfs_set_tags_bytes(bytes)
3567
def lock_read(self):
3568
"""Lock the branch for read operations.
3570
:return: A breezy.lock.LogicalLockResult.
3572
self.repository.lock_read()
3573
if not self._lock_mode:
3574
self._note_lock('r')
3575
self._lock_mode = 'r'
3576
self._lock_count = 1
3577
if self._real_branch is not None:
3578
self._real_branch.lock_read()
3580
self._lock_count += 1
3581
return lock.LogicalLockResult(self.unlock)
3583
def _remote_lock_write(self, token):
3585
branch_token = repo_token = ''
3587
branch_token = token
3588
repo_token = self.repository.lock_write().repository_token
3589
self.repository.unlock()
3590
err_context = {'token': token}
3592
response = self._call(
3593
'Branch.lock_write', self._remote_path(), branch_token,
3594
repo_token or '', **err_context)
3595
except errors.LockContention as e:
3596
# The LockContention from the server doesn't have any
3597
# information about the lock_url. We re-raise LockContention
3598
# with valid lock_url.
3599
raise errors.LockContention('(remote lock)',
3600
self.repository.base.split('.bzr/')[0])
3601
if response[0] != 'ok':
3602
raise errors.UnexpectedSmartServerResponse(response)
3603
ok, branch_token, repo_token = response
3604
return branch_token, repo_token
3606
def lock_write(self, token=None):
3607
if not self._lock_mode:
3608
self._note_lock('w')
3609
# Lock the branch and repo in one remote call.
3610
remote_tokens = self._remote_lock_write(token)
3611
self._lock_token, self._repo_lock_token = remote_tokens
3612
if not self._lock_token:
3613
raise SmartProtocolError('Remote server did not return a token!')
3614
# Tell the self.repository object that it is locked.
3615
self.repository.lock_write(
3616
self._repo_lock_token, _skip_rpc=True)
3618
if self._real_branch is not None:
3619
self._real_branch.lock_write(token=self._lock_token)
3620
if token is not None:
3621
self._leave_lock = True
3623
self._leave_lock = False
3624
self._lock_mode = 'w'
3625
self._lock_count = 1
3626
elif self._lock_mode == 'r':
3627
raise errors.ReadOnlyError(self)
3629
if token is not None:
3630
# A token was given to lock_write, and we're relocking, so
3631
# check that the given token actually matches the one we
3633
if token != self._lock_token:
3634
raise errors.TokenMismatch(token, self._lock_token)
3635
self._lock_count += 1
3636
# Re-lock the repository too.
3637
self.repository.lock_write(self._repo_lock_token)
3638
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3640
def _unlock(self, branch_token, repo_token):
3641
err_context = {'token': str((branch_token, repo_token))}
3642
response = self._call(
3643
'Branch.unlock', self._remote_path(), branch_token,
3644
repo_token or '', **err_context)
3645
if response == ('ok',):
3647
raise errors.UnexpectedSmartServerResponse(response)
3649
@only_raises(errors.LockNotHeld, errors.LockBroken)
3652
self._lock_count -= 1
3653
if not self._lock_count:
3654
if self.conf_store is not None:
3655
self.conf_store.save_changes()
3656
self._clear_cached_state()
3657
mode = self._lock_mode
3658
self._lock_mode = None
3659
if self._real_branch is not None:
3660
if (not self._leave_lock and mode == 'w' and
3661
self._repo_lock_token):
3662
# If this RemoteBranch will remove the physical lock
3663
# for the repository, make sure the _real_branch
3664
# doesn't do it first. (Because the _real_branch's
3665
# repository is set to be the RemoteRepository.)
3666
self._real_branch.repository.leave_lock_in_place()
3667
self._real_branch.unlock()
3669
# Only write-locked branched need to make a remote method
3670
# call to perform the unlock.
3672
if not self._lock_token:
3673
raise AssertionError('Locked, but no token!')
3674
branch_token = self._lock_token
3675
repo_token = self._repo_lock_token
3676
self._lock_token = None
3677
self._repo_lock_token = None
3678
if not self._leave_lock:
3679
self._unlock(branch_token, repo_token)
3681
self.repository.unlock()
3683
def break_lock(self):
3685
response = self._call(
3686
'Branch.break_lock', self._remote_path())
3687
except errors.UnknownSmartMethod:
3689
return self._real_branch.break_lock()
3690
if response != ('ok',):
3691
raise errors.UnexpectedSmartServerResponse(response)
3693
def leave_lock_in_place(self):
3694
if not self._lock_token:
3695
raise NotImplementedError(self.leave_lock_in_place)
3696
self._leave_lock = True
3698
def dont_leave_lock_in_place(self):
3699
if not self._lock_token:
3700
raise NotImplementedError(self.dont_leave_lock_in_place)
3701
self._leave_lock = False
3704
def get_rev_id(self, revno, history=None):
3706
return _mod_revision.NULL_REVISION
3707
last_revision_info = self.last_revision_info()
3708
ok, result = self.repository.get_rev_id_for_revno(
3709
revno, last_revision_info)
3712
missing_parent = result[1]
3713
# Either the revision named by the server is missing, or its parent
3714
# is. Call get_parent_map to determine which, so that we report a
3716
parent_map = self.repository.get_parent_map([missing_parent])
3717
if missing_parent in parent_map:
3718
missing_parent = parent_map[missing_parent]
3719
raise errors.RevisionNotPresent(missing_parent, self.repository)
3721
def _read_last_revision_info(self):
3722
response = self._call('Branch.last_revision_info', self._remote_path())
3723
if response[0] != 'ok':
3724
raise SmartProtocolError('unexpected response code %s' % (response,))
3725
revno = int(response[1])
3726
last_revision = response[2]
3727
return (revno, last_revision)
3729
def _gen_revision_history(self):
3730
"""See Branch._gen_revision_history()."""
3731
if self._is_stacked:
3733
return self._real_branch._gen_revision_history()
3734
response_tuple, response_handler = self._call_expecting_body(
3735
'Branch.revision_history', self._remote_path())
3736
if response_tuple[0] != 'ok':
3737
raise errors.UnexpectedSmartServerResponse(response_tuple)
3738
result = response_handler.read_body_bytes().split('\x00')
3743
def _remote_path(self):
3744
return self.bzrdir._path_for_remote_call(self._client)
3746
def _set_last_revision_descendant(self, revision_id, other_branch,
3747
allow_diverged=False, allow_overwrite_descendant=False):
3748
# This performs additional work to meet the hook contract; while its
3749
# undesirable, we have to synthesise the revno to call the hook, and
3750
# not calling the hook is worse as it means changes can't be prevented.
3751
# Having calculated this though, we can't just call into
3752
# set_last_revision_info as a simple call, because there is a set_rh
3753
# hook that some folk may still be using.
3754
old_revno, old_revid = self.last_revision_info()
3755
history = self._lefthand_history(revision_id)
3756
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3757
err_context = {'other_branch': other_branch}
3758
response = self._call('Branch.set_last_revision_ex',
3759
self._remote_path(), self._lock_token, self._repo_lock_token,
3760
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3762
self._clear_cached_state()
3763
if len(response) != 3 and response[0] != 'ok':
3764
raise errors.UnexpectedSmartServerResponse(response)
3765
new_revno, new_revision_id = response[1:]
3766
self._last_revision_info_cache = new_revno, new_revision_id
3767
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3768
if self._real_branch is not None:
3769
cache = new_revno, new_revision_id
3770
self._real_branch._last_revision_info_cache = cache
3772
def _set_last_revision(self, revision_id):
3773
old_revno, old_revid = self.last_revision_info()
3774
# This performs additional work to meet the hook contract; while its
3775
# undesirable, we have to synthesise the revno to call the hook, and
3776
# not calling the hook is worse as it means changes can't be prevented.
3777
# Having calculated this though, we can't just call into
3778
# set_last_revision_info as a simple call, because there is a set_rh
3779
# hook that some folk may still be using.
3780
history = self._lefthand_history(revision_id)
3781
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3782
self._clear_cached_state()
3783
response = self._call('Branch.set_last_revision',
3784
self._remote_path(), self._lock_token, self._repo_lock_token,
3786
if response != ('ok',):
3787
raise errors.UnexpectedSmartServerResponse(response)
3788
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3790
def _get_parent_location(self):
3791
medium = self._client._medium
3792
if medium._is_remote_before((1, 13)):
3793
return self._vfs_get_parent_location()
3795
response = self._call('Branch.get_parent', self._remote_path())
3796
except errors.UnknownSmartMethod:
3797
medium._remember_remote_is_before((1, 13))
3798
return self._vfs_get_parent_location()
3799
if len(response) != 1:
3800
raise errors.UnexpectedSmartServerResponse(response)
3801
parent_location = response[0]
3802
if parent_location == '':
3804
return parent_location
3806
def _vfs_get_parent_location(self):
3808
return self._real_branch._get_parent_location()
3810
def _set_parent_location(self, url):
3811
medium = self._client._medium
3812
if medium._is_remote_before((1, 15)):
3813
return self._vfs_set_parent_location(url)
3815
call_url = url or ''
3816
if not isinstance(call_url, str):
3817
raise AssertionError('url must be a str or None (%s)' % url)
3818
response = self._call('Branch.set_parent_location',
3819
self._remote_path(), self._lock_token, self._repo_lock_token,
3821
except errors.UnknownSmartMethod:
3822
medium._remember_remote_is_before((1, 15))
3823
return self._vfs_set_parent_location(url)
3825
raise errors.UnexpectedSmartServerResponse(response)
3827
def _vfs_set_parent_location(self, url):
3829
return self._real_branch._set_parent_location(url)
3832
def pull(self, source, overwrite=False, stop_revision=None,
3834
self._clear_cached_state_of_remote_branch_only()
3836
return self._real_branch.pull(
3837
source, overwrite=overwrite, stop_revision=stop_revision,
3838
_override_hook_target=self, **kwargs)
3841
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3843
return self._real_branch.push(
3844
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3845
_override_hook_source_branch=self)
3847
def peek_lock_mode(self):
3848
return self._lock_mode
3850
def is_locked(self):
3851
return self._lock_count >= 1
3854
def revision_id_to_dotted_revno(self, revision_id):
3855
"""Given a revision id, return its dotted revno.
3857
:return: a tuple like (1,) or (400,1,3).
3860
response = self._call('Branch.revision_id_to_revno',
3861
self._remote_path(), revision_id)
3862
except errors.UnknownSmartMethod:
3864
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3865
if response[0] == 'ok':
3866
return tuple([int(x) for x in response[1:]])
3868
raise errors.UnexpectedSmartServerResponse(response)
3871
def revision_id_to_revno(self, revision_id):
3872
"""Given a revision id on the branch mainline, return its revno.
3877
response = self._call('Branch.revision_id_to_revno',
3878
self._remote_path(), revision_id)
3879
except errors.UnknownSmartMethod:
3881
return self._real_branch.revision_id_to_revno(revision_id)
3882
if response[0] == 'ok':
3883
if len(response) == 2:
3884
return int(response[1])
3885
raise NoSuchRevision(self, revision_id)
3887
raise errors.UnexpectedSmartServerResponse(response)
3890
def set_last_revision_info(self, revno, revision_id):
3891
# XXX: These should be returned by the set_last_revision_info verb
3892
old_revno, old_revid = self.last_revision_info()
3893
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3894
if not revision_id or not isinstance(revision_id, basestring):
3895
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3897
response = self._call('Branch.set_last_revision_info',
3898
self._remote_path(), self._lock_token, self._repo_lock_token,
3899
str(revno), revision_id)
3900
except errors.UnknownSmartMethod:
3902
self._clear_cached_state_of_remote_branch_only()
3903
self._real_branch.set_last_revision_info(revno, revision_id)
3904
self._last_revision_info_cache = revno, revision_id
3906
if response == ('ok',):
3907
self._clear_cached_state()
3908
self._last_revision_info_cache = revno, revision_id
3909
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3910
# Update the _real_branch's cache too.
3911
if self._real_branch is not None:
3912
cache = self._last_revision_info_cache
3913
self._real_branch._last_revision_info_cache = cache
3915
raise errors.UnexpectedSmartServerResponse(response)
3918
def generate_revision_history(self, revision_id, last_rev=None,
3920
medium = self._client._medium
3921
if not medium._is_remote_before((1, 6)):
3922
# Use a smart method for 1.6 and above servers
3924
self._set_last_revision_descendant(revision_id, other_branch,
3925
allow_diverged=True, allow_overwrite_descendant=True)
3927
except errors.UnknownSmartMethod:
3928
medium._remember_remote_is_before((1, 6))
3929
self._clear_cached_state_of_remote_branch_only()
3930
graph = self.repository.get_graph()
3931
(last_revno, last_revid) = self.last_revision_info()
3932
known_revision_ids = [
3933
(last_revid, last_revno),
3934
(_mod_revision.NULL_REVISION, 0),
3936
if last_rev is not None:
3937
if not graph.is_ancestor(last_rev, revision_id):
3938
# our previous tip is not merged into stop_revision
3939
raise errors.DivergedBranches(self, other_branch)
3940
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
3941
self.set_last_revision_info(revno, revision_id)
3943
def set_push_location(self, location):
3944
self._set_config_location('push_location', location)
3946
def heads_to_fetch(self):
3947
if self._format._use_default_local_heads_to_fetch():
3948
# We recognise this format, and its heads-to-fetch implementation
3949
# is the default one (tip + tags). In this case it's cheaper to
3950
# just use the default implementation rather than a special RPC as
3951
# the tip and tags data is cached.
3952
return branch.Branch.heads_to_fetch(self)
3953
medium = self._client._medium
3954
if medium._is_remote_before((2, 4)):
3955
return self._vfs_heads_to_fetch()
3957
return self._rpc_heads_to_fetch()
3958
except errors.UnknownSmartMethod:
3959
medium._remember_remote_is_before((2, 4))
3960
return self._vfs_heads_to_fetch()
3962
def _rpc_heads_to_fetch(self):
3963
response = self._call('Branch.heads_to_fetch', self._remote_path())
3964
if len(response) != 2:
3965
raise errors.UnexpectedSmartServerResponse(response)
3966
must_fetch, if_present_fetch = response
3967
return set(must_fetch), set(if_present_fetch)
3969
def _vfs_heads_to_fetch(self):
3971
return self._real_branch.heads_to_fetch()
3974
class RemoteConfig(object):
3975
"""A Config that reads and writes from smart verbs.
3977
It is a low-level object that considers config data to be name/value pairs
3978
that may be associated with a section. Assigning meaning to the these
3979
values is done at higher levels like breezy.config.TreeConfig.
3982
def get_option(self, name, section=None, default=None):
3983
"""Return the value associated with a named option.
3985
:param name: The name of the value
3986
:param section: The section the option is in (if any)
3987
:param default: The value to return if the value is not set
3988
:return: The value or default value
3991
configobj = self._get_configobj()
3994
section_obj = configobj
3997
section_obj = configobj[section]
4000
if section_obj is None:
4003
value = section_obj.get(name, default)
4004
except errors.UnknownSmartMethod:
4005
value = self._vfs_get_option(name, section, default)
4006
for hook in _mod_config.OldConfigHooks['get']:
4007
hook(self, name, value)
4010
def _response_to_configobj(self, response):
4011
if len(response[0]) and response[0][0] != 'ok':
4012
raise errors.UnexpectedSmartServerResponse(response)
4013
lines = response[1].read_body_bytes().splitlines()
4014
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4015
for hook in _mod_config.OldConfigHooks['load']:
4020
class RemoteBranchConfig(RemoteConfig):
4021
"""A RemoteConfig for Branches."""
4023
def __init__(self, branch):
4024
self._branch = branch
4026
def _get_configobj(self):
4027
path = self._branch._remote_path()
4028
response = self._branch._client.call_expecting_body(
4029
'Branch.get_config_file', path)
4030
return self._response_to_configobj(response)
4032
def set_option(self, value, name, section=None):
4033
"""Set the value associated with a named option.
4035
:param value: The value to set
4036
:param name: The name of the value to set
4037
:param section: The section the option is in (if any)
4039
medium = self._branch._client._medium
4040
if medium._is_remote_before((1, 14)):
4041
return self._vfs_set_option(value, name, section)
4042
if isinstance(value, dict):
4043
if medium._is_remote_before((2, 2)):
4044
return self._vfs_set_option(value, name, section)
4045
return self._set_config_option_dict(value, name, section)
4047
return self._set_config_option(value, name, section)
4049
def _set_config_option(self, value, name, section):
4051
path = self._branch._remote_path()
4052
response = self._branch._client.call('Branch.set_config_option',
4053
path, self._branch._lock_token, self._branch._repo_lock_token,
4054
value.encode('utf8'), name, section or '')
4055
except errors.UnknownSmartMethod:
4056
medium = self._branch._client._medium
4057
medium._remember_remote_is_before((1, 14))
4058
return self._vfs_set_option(value, name, section)
4060
raise errors.UnexpectedSmartServerResponse(response)
4062
def _serialize_option_dict(self, option_dict):
4064
for key, value in option_dict.items():
4065
if isinstance(key, unicode):
4066
key = key.encode('utf8')
4067
if isinstance(value, unicode):
4068
value = value.encode('utf8')
4069
utf8_dict[key] = value
4070
return bencode.bencode(utf8_dict)
4072
def _set_config_option_dict(self, value, name, section):
4074
path = self._branch._remote_path()
4075
serialised_dict = self._serialize_option_dict(value)
4076
response = self._branch._client.call(
4077
'Branch.set_config_option_dict',
4078
path, self._branch._lock_token, self._branch._repo_lock_token,
4079
serialised_dict, name, section or '')
4080
except errors.UnknownSmartMethod:
4081
medium = self._branch._client._medium
4082
medium._remember_remote_is_before((2, 2))
4083
return self._vfs_set_option(value, name, section)
4085
raise errors.UnexpectedSmartServerResponse(response)
4087
def _real_object(self):
4088
self._branch._ensure_real()
4089
return self._branch._real_branch
4091
def _vfs_set_option(self, value, name, section=None):
4092
return self._real_object()._get_config().set_option(
4093
value, name, section)
4096
class RemoteBzrDirConfig(RemoteConfig):
4097
"""A RemoteConfig for BzrDirs."""
4099
def __init__(self, bzrdir):
4100
self._bzrdir = bzrdir
4102
def _get_configobj(self):
4103
medium = self._bzrdir._client._medium
4104
verb = 'BzrDir.get_config_file'
4105
if medium._is_remote_before((1, 15)):
4106
raise errors.UnknownSmartMethod(verb)
4107
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4108
response = self._bzrdir._call_expecting_body(
4110
return self._response_to_configobj(response)
4112
def _vfs_get_option(self, name, section, default):
4113
return self._real_object()._get_config().get_option(
4114
name, section, default)
4116
def set_option(self, value, name, section=None):
4117
"""Set the value associated with a named option.
4119
:param value: The value to set
4120
:param name: The name of the value to set
4121
:param section: The section the option is in (if any)
4123
return self._real_object()._get_config().set_option(
4124
value, name, section)
4126
def _real_object(self):
4127
self._bzrdir._ensure_real()
4128
return self._bzrdir._real_bzrdir
4131
def _extract_tar(tar, to_dir):
4132
"""Extract all the contents of a tarfile object.
4134
A replacement for extractall, which is not present in python2.4
4137
tar.extract(tarinfo, to_dir)
4140
error_translators = registry.Registry()
4141
no_context_error_translators = registry.Registry()
4144
def _translate_error(err, **context):
4145
"""Translate an ErrorFromSmartServer into a more useful error.
4147
Possible context keys:
4155
If the error from the server doesn't match a known pattern, then
4156
UnknownErrorFromSmartServer is raised.
4160
return context[name]
4161
except KeyError as key_err:
4162
mutter('Missing key %r in context %r', key_err.args[0], context)
4165
"""Get the path from the context if present, otherwise use first error
4169
return context['path']
4170
except KeyError as key_err:
4172
return err.error_args[0]
4173
except IndexError as idx_err:
4175
'Missing key %r in context %r', key_err.args[0], context)
4179
translator = error_translators.get(err.error_verb)
4183
raise translator(err, find, get_path)
4185
translator = no_context_error_translators.get(err.error_verb)
4187
raise errors.UnknownErrorFromSmartServer(err)
4189
raise translator(err)
4192
error_translators.register('NoSuchRevision',
4193
lambda err, find, get_path: NoSuchRevision(
4194
find('branch'), err.error_args[0]))
4195
error_translators.register('nosuchrevision',
4196
lambda err, find, get_path: NoSuchRevision(
4197
find('repository'), err.error_args[0]))
4199
def _translate_nobranch_error(err, find, get_path):
4200
if len(err.error_args) >= 1:
4201
extra = err.error_args[0]
4204
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4207
error_translators.register('nobranch', _translate_nobranch_error)
4208
error_translators.register('norepository',
4209
lambda err, find, get_path: errors.NoRepositoryPresent(
4211
error_translators.register('UnlockableTransport',
4212
lambda err, find, get_path: errors.UnlockableTransport(
4213
find('bzrdir').root_transport))
4214
error_translators.register('TokenMismatch',
4215
lambda err, find, get_path: errors.TokenMismatch(
4216
find('token'), '(remote token)'))
4217
error_translators.register('Diverged',
4218
lambda err, find, get_path: errors.DivergedBranches(
4219
find('branch'), find('other_branch')))
4220
error_translators.register('NotStacked',
4221
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4223
def _translate_PermissionDenied(err, find, get_path):
4225
if len(err.error_args) >= 2:
4226
extra = err.error_args[1]
4229
return errors.PermissionDenied(path, extra=extra)
4231
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4232
error_translators.register('ReadError',
4233
lambda err, find, get_path: errors.ReadError(get_path()))
4234
error_translators.register('NoSuchFile',
4235
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4236
error_translators.register('TokenLockingNotSupported',
4237
lambda err, find, get_path: errors.TokenLockingNotSupported(
4238
find('repository')))
4239
error_translators.register('UnsuspendableWriteGroup',
4240
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4241
repository=find('repository')))
4242
error_translators.register('UnresumableWriteGroup',
4243
lambda err, find, get_path: errors.UnresumableWriteGroup(
4244
repository=find('repository'), write_groups=err.error_args[0],
4245
reason=err.error_args[1]))
4246
no_context_error_translators.register('IncompatibleRepositories',
4247
lambda err: errors.IncompatibleRepositories(
4248
err.error_args[0], err.error_args[1], err.error_args[2]))
4249
no_context_error_translators.register('LockContention',
4250
lambda err: errors.LockContention('(remote lock)'))
4251
no_context_error_translators.register('LockFailed',
4252
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4253
no_context_error_translators.register('TipChangeRejected',
4254
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4255
no_context_error_translators.register('UnstackableBranchFormat',
4256
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4257
no_context_error_translators.register('UnstackableRepositoryFormat',
4258
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4259
no_context_error_translators.register('FileExists',
4260
lambda err: errors.FileExists(err.error_args[0]))
4261
no_context_error_translators.register('DirectoryNotEmpty',
4262
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4264
def _translate_short_readv_error(err):
4265
args = err.error_args
4266
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4269
no_context_error_translators.register('ShortReadvError',
4270
_translate_short_readv_error)
4272
def _translate_unicode_error(err):
4273
encoding = str(err.error_args[0]) # encoding must always be a string
4274
val = err.error_args[1]
4275
start = int(err.error_args[2])
4276
end = int(err.error_args[3])
4277
reason = str(err.error_args[4]) # reason must always be a string
4278
if val.startswith('u:'):
4279
val = val[2:].decode('utf-8')
4280
elif val.startswith('s:'):
4281
val = val[2:].decode('base64')
4282
if err.error_verb == 'UnicodeDecodeError':
4283
raise UnicodeDecodeError(encoding, val, start, end, reason)
4284
elif err.error_verb == 'UnicodeEncodeError':
4285
raise UnicodeEncodeError(encoding, val, start, end, reason)
4287
no_context_error_translators.register('UnicodeEncodeError',
4288
_translate_unicode_error)
4289
no_context_error_translators.register('UnicodeDecodeError',
4290
_translate_unicode_error)
4291
no_context_error_translators.register('ReadOnlyError',
4292
lambda err: errors.TransportNotPossible('readonly transport'))
4293
no_context_error_translators.register('MemoryError',
4294
lambda err: errors.BzrError("remote server out of memory\n"
4295
"Retry non-remotely, or contact the server admin for details."))
4296
no_context_error_translators.register('RevisionNotPresent',
4297
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4299
no_context_error_translators.register('BzrCheckError',
4300
lambda err: errors.BzrCheckError(msg=err.error_args[0]))