1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
25
bzrdir as _mod_bzrdir,
26
config as _mod_config,
37
repository as _mod_repository,
38
revision as _mod_revision,
41
testament as _mod_testament,
46
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
47
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
48
from bzrlib.errors import (
52
from bzrlib.i18n import gettext
53
from bzrlib.inventory import Inventory
54
from bzrlib.lockable_files import LockableFiles
55
from bzrlib.smart import client, vfs, repository as smart_repo
56
from bzrlib.smart.client import _SmartClient
57
from bzrlib.revision import NULL_REVISION
58
from bzrlib.revisiontree import InventoryRevisionTree
59
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
60
from bzrlib.serializer import format_registry as serializer_format_registry
61
from bzrlib.trace import mutter, note, warning, log_exception_quietly
62
from bzrlib.versionedfile import ChunkedContentFactory, FulltextContentFactory
65
_DEFAULT_SEARCH_DEPTH = 100
68
class _RpcHelper(object):
69
"""Mixin class that helps with issuing RPCs."""
71
def _call(self, method, *args, **err_context):
73
return self._client.call(method, *args)
74
except errors.ErrorFromSmartServer, err:
75
self._translate_error(err, **err_context)
77
def _call_expecting_body(self, method, *args, **err_context):
79
return self._client.call_expecting_body(method, *args)
80
except errors.ErrorFromSmartServer, err:
81
self._translate_error(err, **err_context)
83
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
85
return self._client.call_with_body_bytes(method, args, body_bytes)
86
except errors.ErrorFromSmartServer, err:
87
self._translate_error(err, **err_context)
89
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
92
return self._client.call_with_body_bytes_expecting_body(
93
method, args, body_bytes)
94
except errors.ErrorFromSmartServer, err:
95
self._translate_error(err, **err_context)
98
def response_tuple_to_repo_format(response):
99
"""Convert a response tuple describing a repository format to a format."""
100
format = RemoteRepositoryFormat()
101
format._rich_root_data = (response[0] == 'yes')
102
format._supports_tree_reference = (response[1] == 'yes')
103
format._supports_external_lookups = (response[2] == 'yes')
104
format._network_name = response[3]
108
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
109
# does not have to be imported unless a remote format is involved.
111
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
112
"""Format representing bzrdirs accessed via a smart server"""
114
supports_workingtrees = False
117
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
118
# XXX: It's a bit ugly that the network name is here, because we'd
119
# like to believe that format objects are stateless or at least
120
# immutable, However, we do at least avoid mutating the name after
121
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
122
self._network_name = None
125
return "%s(_network_name=%r)" % (self.__class__.__name__,
128
def get_format_description(self):
129
if self._network_name:
131
real_format = controldir.network_format_registry.get(
136
return 'Remote: ' + real_format.get_format_description()
137
return 'bzr remote bzrdir'
139
def get_format_string(self):
140
raise NotImplementedError(self.get_format_string)
142
def network_name(self):
143
if self._network_name:
144
return self._network_name
146
raise AssertionError("No network name set.")
148
def initialize_on_transport(self, transport):
150
# hand off the request to the smart server
151
client_medium = transport.get_smart_medium()
152
except errors.NoSmartMedium:
153
# TODO: lookup the local format from a server hint.
154
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
155
return local_dir_format.initialize_on_transport(transport)
156
client = _SmartClient(client_medium)
157
path = client.remote_path_from_transport(transport)
159
response = client.call('BzrDirFormat.initialize', path)
160
except errors.ErrorFromSmartServer, err:
161
_translate_error(err, path=path)
162
if response[0] != 'ok':
163
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
164
format = RemoteBzrDirFormat()
165
self._supply_sub_formats_to(format)
166
return RemoteBzrDir(transport, format)
168
def parse_NoneTrueFalse(self, arg):
175
raise AssertionError("invalid arg %r" % arg)
177
def _serialize_NoneTrueFalse(self, arg):
184
def _serialize_NoneString(self, arg):
187
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
188
create_prefix=False, force_new_repo=False, stacked_on=None,
189
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
192
# hand off the request to the smart server
193
client_medium = transport.get_smart_medium()
194
except errors.NoSmartMedium:
197
# Decline to open it if the server doesn't support our required
198
# version (3) so that the VFS-based transport will do it.
199
if client_medium.should_probe():
201
server_version = client_medium.protocol_version()
202
if server_version != '2':
206
except errors.SmartProtocolError:
207
# Apparently there's no usable smart server there, even though
208
# the medium supports the smart protocol.
213
client = _SmartClient(client_medium)
214
path = client.remote_path_from_transport(transport)
215
if client_medium._is_remote_before((1, 16)):
218
# TODO: lookup the local format from a server hint.
219
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
220
self._supply_sub_formats_to(local_dir_format)
221
return local_dir_format.initialize_on_transport_ex(transport,
222
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
223
force_new_repo=force_new_repo, stacked_on=stacked_on,
224
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
225
make_working_trees=make_working_trees, shared_repo=shared_repo,
227
return self._initialize_on_transport_ex_rpc(client, path, transport,
228
use_existing_dir, create_prefix, force_new_repo, stacked_on,
229
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
231
def _initialize_on_transport_ex_rpc(self, client, path, transport,
232
use_existing_dir, create_prefix, force_new_repo, stacked_on,
233
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
235
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
236
args.append(self._serialize_NoneTrueFalse(create_prefix))
237
args.append(self._serialize_NoneTrueFalse(force_new_repo))
238
args.append(self._serialize_NoneString(stacked_on))
239
# stack_on_pwd is often/usually our transport
242
stack_on_pwd = transport.relpath(stack_on_pwd)
245
except errors.PathNotChild:
247
args.append(self._serialize_NoneString(stack_on_pwd))
248
args.append(self._serialize_NoneString(repo_format_name))
249
args.append(self._serialize_NoneTrueFalse(make_working_trees))
250
args.append(self._serialize_NoneTrueFalse(shared_repo))
251
request_network_name = self._network_name or \
252
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
254
response = client.call('BzrDirFormat.initialize_ex_1.16',
255
request_network_name, path, *args)
256
except errors.UnknownSmartMethod:
257
client._medium._remember_remote_is_before((1,16))
258
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
259
self._supply_sub_formats_to(local_dir_format)
260
return local_dir_format.initialize_on_transport_ex(transport,
261
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
262
force_new_repo=force_new_repo, stacked_on=stacked_on,
263
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
264
make_working_trees=make_working_trees, shared_repo=shared_repo,
266
except errors.ErrorFromSmartServer, err:
267
_translate_error(err, path=path)
268
repo_path = response[0]
269
bzrdir_name = response[6]
270
require_stacking = response[7]
271
require_stacking = self.parse_NoneTrueFalse(require_stacking)
272
format = RemoteBzrDirFormat()
273
format._network_name = bzrdir_name
274
self._supply_sub_formats_to(format)
275
bzrdir = RemoteBzrDir(transport, format, _client=client)
277
repo_format = response_tuple_to_repo_format(response[1:])
281
repo_bzrdir_format = RemoteBzrDirFormat()
282
repo_bzrdir_format._network_name = response[5]
283
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
287
final_stack = response[8] or None
288
final_stack_pwd = response[9] or None
290
final_stack_pwd = urlutils.join(
291
transport.base, final_stack_pwd)
292
remote_repo = RemoteRepository(repo_bzr, repo_format)
293
if len(response) > 10:
294
# Updated server verb that locks remotely.
295
repo_lock_token = response[10] or None
296
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
298
remote_repo.dont_leave_lock_in_place()
300
remote_repo.lock_write()
301
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
302
final_stack_pwd, require_stacking)
303
policy.acquire_repository()
307
bzrdir._format.set_branch_format(self.get_branch_format())
309
# The repo has already been created, but we need to make sure that
310
# we'll make a stackable branch.
311
bzrdir._format.require_stacking(_skip_repo=True)
312
return remote_repo, bzrdir, require_stacking, policy
314
def _open(self, transport):
315
return RemoteBzrDir(transport, self)
317
def __eq__(self, other):
318
if not isinstance(other, RemoteBzrDirFormat):
320
return self.get_format_description() == other.get_format_description()
322
def __return_repository_format(self):
323
# Always return a RemoteRepositoryFormat object, but if a specific bzr
324
# repository format has been asked for, tell the RemoteRepositoryFormat
325
# that it should use that for init() etc.
326
result = RemoteRepositoryFormat()
327
custom_format = getattr(self, '_repository_format', None)
329
if isinstance(custom_format, RemoteRepositoryFormat):
332
# We will use the custom format to create repositories over the
333
# wire; expose its details like rich_root_data for code to
335
result._custom_format = custom_format
338
def get_branch_format(self):
339
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
340
if not isinstance(result, RemoteBranchFormat):
341
new_result = RemoteBranchFormat()
342
new_result._custom_format = result
344
self.set_branch_format(new_result)
348
repository_format = property(__return_repository_format,
349
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
352
class RemoteControlStore(_mod_config.IniFileStore):
353
"""Control store which attempts to use HPSS calls to retrieve control store.
355
Note that this is specific to bzr-based formats.
358
def __init__(self, bzrdir):
359
super(RemoteControlStore, self).__init__()
361
self._real_store = None
363
def lock_write(self, token=None):
365
return self._real_store.lock_write(token)
369
return self._real_store.unlock()
373
# We need to be able to override the undecorated implementation
374
self.save_without_locking()
376
def save_without_locking(self):
377
super(RemoteControlStore, self).save()
379
def _ensure_real(self):
380
self.bzrdir._ensure_real()
381
if self._real_store is None:
382
self._real_store = _mod_config.ControlStore(self.bzrdir)
384
def external_url(self):
385
return self.bzrdir.user_url
387
def _load_content(self):
388
medium = self.bzrdir._client._medium
389
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
391
response, handler = self.bzrdir._call_expecting_body(
392
'BzrDir.get_config_file', path)
393
except errors.UnknownSmartMethod:
395
return self._real_store._load_content()
396
if len(response) and response[0] != 'ok':
397
raise errors.UnexpectedSmartServerResponse(response)
398
return handler.read_body_bytes()
400
def _save_content(self, content):
401
# FIXME JRV 2011-11-22: Ideally this should use a
402
# HPSS call too, but at the moment it is not possible
403
# to write lock control directories.
405
return self._real_store._save_content(content)
408
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
409
"""Control directory on a remote server, accessed via bzr:// or similar."""
411
def __init__(self, transport, format, _client=None, _force_probe=False):
412
"""Construct a RemoteBzrDir.
414
:param _client: Private parameter for testing. Disables probing and the
415
use of a real bzrdir.
417
_mod_bzrdir.BzrDir.__init__(self, transport, format)
418
# this object holds a delegated bzrdir that uses file-level operations
419
# to talk to the other side
420
self._real_bzrdir = None
421
self._has_working_tree = None
422
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
423
# create_branch for details.
424
self._next_open_branch_result = None
427
medium = transport.get_smart_medium()
428
self._client = client._SmartClient(medium)
430
self._client = _client
437
return '%s(%r)' % (self.__class__.__name__, self._client)
439
def _probe_bzrdir(self):
440
medium = self._client._medium
441
path = self._path_for_remote_call(self._client)
442
if medium._is_remote_before((2, 1)):
446
self._rpc_open_2_1(path)
448
except errors.UnknownSmartMethod:
449
medium._remember_remote_is_before((2, 1))
452
def _rpc_open_2_1(self, path):
453
response = self._call('BzrDir.open_2.1', path)
454
if response == ('no',):
455
raise errors.NotBranchError(path=self.root_transport.base)
456
elif response[0] == 'yes':
457
if response[1] == 'yes':
458
self._has_working_tree = True
459
elif response[1] == 'no':
460
self._has_working_tree = False
462
raise errors.UnexpectedSmartServerResponse(response)
464
raise errors.UnexpectedSmartServerResponse(response)
466
def _rpc_open(self, path):
467
response = self._call('BzrDir.open', path)
468
if response not in [('yes',), ('no',)]:
469
raise errors.UnexpectedSmartServerResponse(response)
470
if response == ('no',):
471
raise errors.NotBranchError(path=self.root_transport.base)
473
def _ensure_real(self):
474
"""Ensure that there is a _real_bzrdir set.
476
Used before calls to self._real_bzrdir.
478
if not self._real_bzrdir:
479
if 'hpssvfs' in debug.debug_flags:
481
warning('VFS BzrDir access triggered\n%s',
482
''.join(traceback.format_stack()))
483
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
484
self.root_transport, probers=[_mod_bzrdir.BzrProber])
485
self._format._network_name = \
486
self._real_bzrdir._format.network_name()
488
def _translate_error(self, err, **context):
489
_translate_error(err, bzrdir=self, **context)
491
def break_lock(self):
492
# Prevent aliasing problems in the next_open_branch_result cache.
493
# See create_branch for rationale.
494
self._next_open_branch_result = None
495
return _mod_bzrdir.BzrDir.break_lock(self)
497
def _vfs_checkout_metadir(self):
499
return self._real_bzrdir.checkout_metadir()
501
def checkout_metadir(self):
502
"""Retrieve the controldir format to use for checkouts of this one.
504
medium = self._client._medium
505
if medium._is_remote_before((2, 5)):
506
return self._vfs_checkout_metadir()
507
path = self._path_for_remote_call(self._client)
509
response = self._client.call('BzrDir.checkout_metadir',
511
except errors.UnknownSmartMethod:
512
medium._remember_remote_is_before((2, 5))
513
return self._vfs_checkout_metadir()
514
if len(response) != 3:
515
raise errors.UnexpectedSmartServerResponse(response)
516
control_name, repo_name, branch_name = response
518
format = controldir.network_format_registry.get(control_name)
520
raise errors.UnknownFormatError(kind='control',
524
repo_format = _mod_repository.network_format_registry.get(
527
raise errors.UnknownFormatError(kind='repository',
529
format.repository_format = repo_format
532
format.set_branch_format(
533
branch.network_format_registry.get(branch_name))
535
raise errors.UnknownFormatError(kind='branch',
539
def _vfs_cloning_metadir(self, require_stacking=False):
541
return self._real_bzrdir.cloning_metadir(
542
require_stacking=require_stacking)
544
def cloning_metadir(self, require_stacking=False):
545
medium = self._client._medium
546
if medium._is_remote_before((1, 13)):
547
return self._vfs_cloning_metadir(require_stacking=require_stacking)
548
verb = 'BzrDir.cloning_metadir'
553
path = self._path_for_remote_call(self._client)
555
response = self._call(verb, path, stacking)
556
except errors.UnknownSmartMethod:
557
medium._remember_remote_is_before((1, 13))
558
return self._vfs_cloning_metadir(require_stacking=require_stacking)
559
except errors.UnknownErrorFromSmartServer, err:
560
if err.error_tuple != ('BranchReference',):
562
# We need to resolve the branch reference to determine the
563
# cloning_metadir. This causes unnecessary RPCs to open the
564
# referenced branch (and bzrdir, etc) but only when the caller
565
# didn't already resolve the branch reference.
566
referenced_branch = self.open_branch()
567
return referenced_branch.bzrdir.cloning_metadir()
568
if len(response) != 3:
569
raise errors.UnexpectedSmartServerResponse(response)
570
control_name, repo_name, branch_info = response
571
if len(branch_info) != 2:
572
raise errors.UnexpectedSmartServerResponse(response)
573
branch_ref, branch_name = branch_info
575
format = controldir.network_format_registry.get(control_name)
577
raise errors.UnknownFormatError(kind='control', format=control_name)
581
format.repository_format = _mod_repository.network_format_registry.get(
584
raise errors.UnknownFormatError(kind='repository',
586
if branch_ref == 'ref':
587
# XXX: we need possible_transports here to avoid reopening the
588
# connection to the referenced location
589
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
590
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
591
format.set_branch_format(branch_format)
592
elif branch_ref == 'branch':
595
branch_format = branch.network_format_registry.get(
598
raise errors.UnknownFormatError(kind='branch',
600
format.set_branch_format(branch_format)
602
raise errors.UnexpectedSmartServerResponse(response)
605
def create_repository(self, shared=False):
606
# as per meta1 formats - just delegate to the format object which may
608
result = self._format.repository_format.initialize(self, shared)
609
if not isinstance(result, RemoteRepository):
610
return self.open_repository()
614
def destroy_repository(self):
615
"""See BzrDir.destroy_repository"""
616
path = self._path_for_remote_call(self._client)
618
response = self._call('BzrDir.destroy_repository', path)
619
except errors.UnknownSmartMethod:
621
self._real_bzrdir.destroy_repository()
623
if response[0] != 'ok':
624
raise SmartProtocolError('unexpected response code %s' % (response,))
626
def create_branch(self, name=None, repository=None,
627
append_revisions_only=None):
629
name = self._get_selected_branch()
630
# as per meta1 formats - just delegate to the format object which may
632
real_branch = self._format.get_branch_format().initialize(self,
633
name=name, repository=repository,
634
append_revisions_only=append_revisions_only)
635
if not isinstance(real_branch, RemoteBranch):
636
if not isinstance(repository, RemoteRepository):
637
raise AssertionError(
638
'need a RemoteRepository to use with RemoteBranch, got %r'
640
result = RemoteBranch(self, repository, real_branch, name=name)
643
# BzrDir.clone_on_transport() uses the result of create_branch but does
644
# not return it to its callers; we save approximately 8% of our round
645
# trips by handing the branch we created back to the first caller to
646
# open_branch rather than probing anew. Long term we need a API in
647
# bzrdir that doesn't discard result objects (like result_branch).
649
self._next_open_branch_result = result
652
def destroy_branch(self, name=None):
653
"""See BzrDir.destroy_branch"""
654
path = self._path_for_remote_call(self._client)
660
response = self._call('BzrDir.destroy_branch', path, *args)
661
except errors.UnknownSmartMethod:
663
self._real_bzrdir.destroy_branch(name=name)
664
self._next_open_branch_result = None
666
self._next_open_branch_result = None
667
if response[0] != 'ok':
668
raise SmartProtocolError('unexpected response code %s' % (response,))
670
def create_workingtree(self, revision_id=None, from_branch=None,
671
accelerator_tree=None, hardlink=False):
672
raise errors.NotLocalUrl(self.transport.base)
674
def find_branch_format(self, name=None):
675
"""Find the branch 'format' for this bzrdir.
677
This might be a synthetic object for e.g. RemoteBranch and SVN.
679
b = self.open_branch(name=name)
682
def get_branch_reference(self, name=None):
683
"""See BzrDir.get_branch_reference()."""
685
# XXX JRV20100304: Support opening colocated branches
686
raise errors.NoColocatedBranchSupport(self)
687
response = self._get_branch_reference()
688
if response[0] == 'ref':
693
def _get_branch_reference(self):
694
path = self._path_for_remote_call(self._client)
695
medium = self._client._medium
697
('BzrDir.open_branchV3', (2, 1)),
698
('BzrDir.open_branchV2', (1, 13)),
699
('BzrDir.open_branch', None),
701
for verb, required_version in candidate_calls:
702
if required_version and medium._is_remote_before(required_version):
705
response = self._call(verb, path)
706
except errors.UnknownSmartMethod:
707
if required_version is None:
709
medium._remember_remote_is_before(required_version)
712
if verb == 'BzrDir.open_branch':
713
if response[0] != 'ok':
714
raise errors.UnexpectedSmartServerResponse(response)
715
if response[1] != '':
716
return ('ref', response[1])
718
return ('branch', '')
719
if response[0] not in ('ref', 'branch'):
720
raise errors.UnexpectedSmartServerResponse(response)
723
def _get_tree_branch(self, name=None):
724
"""See BzrDir._get_tree_branch()."""
725
return None, self.open_branch(name=name)
727
def open_branch(self, name=None, unsupported=False,
728
ignore_fallbacks=False, possible_transports=None):
730
name = self._get_selected_branch()
732
raise NotImplementedError('unsupported flag support not implemented yet.')
733
if self._next_open_branch_result is not None:
734
# See create_branch for details.
735
result = self._next_open_branch_result
736
self._next_open_branch_result = None
738
response = self._get_branch_reference()
739
if response[0] == 'ref':
740
# a branch reference, use the existing BranchReference logic.
741
format = BranchReferenceFormat()
742
return format.open(self, name=name, _found=True,
743
location=response[1], ignore_fallbacks=ignore_fallbacks,
744
possible_transports=possible_transports)
745
branch_format_name = response[1]
746
if not branch_format_name:
747
branch_format_name = None
748
format = RemoteBranchFormat(network_name=branch_format_name)
749
return RemoteBranch(self, self.find_repository(), format=format,
750
setup_stacking=not ignore_fallbacks, name=name,
751
possible_transports=possible_transports)
753
def _open_repo_v1(self, path):
754
verb = 'BzrDir.find_repository'
755
response = self._call(verb, path)
756
if response[0] != 'ok':
757
raise errors.UnexpectedSmartServerResponse(response)
758
# servers that only support the v1 method don't support external
761
repo = self._real_bzrdir.open_repository()
762
response = response + ('no', repo._format.network_name())
763
return response, repo
765
def _open_repo_v2(self, path):
766
verb = 'BzrDir.find_repositoryV2'
767
response = self._call(verb, path)
768
if response[0] != 'ok':
769
raise errors.UnexpectedSmartServerResponse(response)
771
repo = self._real_bzrdir.open_repository()
772
response = response + (repo._format.network_name(),)
773
return response, repo
775
def _open_repo_v3(self, path):
776
verb = 'BzrDir.find_repositoryV3'
777
medium = self._client._medium
778
if medium._is_remote_before((1, 13)):
779
raise errors.UnknownSmartMethod(verb)
781
response = self._call(verb, path)
782
except errors.UnknownSmartMethod:
783
medium._remember_remote_is_before((1, 13))
785
if response[0] != 'ok':
786
raise errors.UnexpectedSmartServerResponse(response)
787
return response, None
789
def open_repository(self):
790
path = self._path_for_remote_call(self._client)
792
for probe in [self._open_repo_v3, self._open_repo_v2,
795
response, real_repo = probe(path)
797
except errors.UnknownSmartMethod:
800
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
801
if response[0] != 'ok':
802
raise errors.UnexpectedSmartServerResponse(response)
803
if len(response) != 6:
804
raise SmartProtocolError('incorrect response length %s' % (response,))
805
if response[1] == '':
806
# repo is at this dir.
807
format = response_tuple_to_repo_format(response[2:])
808
# Used to support creating a real format instance when needed.
809
format._creating_bzrdir = self
810
remote_repo = RemoteRepository(self, format)
811
format._creating_repo = remote_repo
812
if real_repo is not None:
813
remote_repo._set_real_repository(real_repo)
816
raise errors.NoRepositoryPresent(self)
818
def has_workingtree(self):
819
if self._has_working_tree is None:
820
path = self._path_for_remote_call(self._client)
822
response = self._call('BzrDir.has_workingtree', path)
823
except errors.UnknownSmartMethod:
825
self._has_working_tree = self._real_bzrdir.has_workingtree()
827
if response[0] not in ('yes', 'no'):
828
raise SmartProtocolError('unexpected response code %s' % (response,))
829
self._has_working_tree = (response[0] == 'yes')
830
return self._has_working_tree
832
def open_workingtree(self, recommend_upgrade=True):
833
if self.has_workingtree():
834
raise errors.NotLocalUrl(self.root_transport)
836
raise errors.NoWorkingTree(self.root_transport.base)
838
def _path_for_remote_call(self, client):
839
"""Return the path to be used for this bzrdir in a remote call."""
840
return urlutils.split_segment_parameters_raw(
841
client.remote_path_from_transport(self.root_transport))[0]
843
def get_branch_transport(self, branch_format, name=None):
845
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
847
def get_repository_transport(self, repository_format):
849
return self._real_bzrdir.get_repository_transport(repository_format)
851
def get_workingtree_transport(self, workingtree_format):
853
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
855
def can_convert_format(self):
856
"""Upgrading of remote bzrdirs is not supported yet."""
859
def needs_format_conversion(self, format):
860
"""Upgrading of remote bzrdirs is not supported yet."""
863
def _get_config(self):
864
return RemoteBzrDirConfig(self)
866
def _get_config_store(self):
867
return RemoteControlStore(self)
870
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
871
"""Format for repositories accessed over a _SmartClient.
873
Instances of this repository are represented by RemoteRepository
876
The RemoteRepositoryFormat is parameterized during construction
877
to reflect the capabilities of the real, remote format. Specifically
878
the attributes rich_root_data and supports_tree_reference are set
879
on a per instance basis, and are not set (and should not be) at
882
:ivar _custom_format: If set, a specific concrete repository format that
883
will be used when initializing a repository with this
884
RemoteRepositoryFormat.
885
:ivar _creating_repo: If set, the repository object that this
886
RemoteRepositoryFormat was created for: it can be called into
887
to obtain data like the network name.
890
_matchingbzrdir = RemoteBzrDirFormat()
891
supports_full_versioned_files = True
892
supports_leaving_lock = True
895
_mod_repository.RepositoryFormat.__init__(self)
896
self._custom_format = None
897
self._network_name = None
898
self._creating_bzrdir = None
899
self._revision_graph_can_have_wrong_parents = None
900
self._supports_chks = None
901
self._supports_external_lookups = None
902
self._supports_tree_reference = None
903
self._supports_funky_characters = None
904
self._supports_nesting_repositories = None
905
self._rich_root_data = None
908
return "%s(_network_name=%r)" % (self.__class__.__name__,
912
def fast_deltas(self):
914
return self._custom_format.fast_deltas
917
def rich_root_data(self):
918
if self._rich_root_data is None:
920
self._rich_root_data = self._custom_format.rich_root_data
921
return self._rich_root_data
924
def supports_chks(self):
925
if self._supports_chks is None:
927
self._supports_chks = self._custom_format.supports_chks
928
return self._supports_chks
931
def supports_external_lookups(self):
932
if self._supports_external_lookups is None:
934
self._supports_external_lookups = \
935
self._custom_format.supports_external_lookups
936
return self._supports_external_lookups
939
def supports_funky_characters(self):
940
if self._supports_funky_characters is None:
942
self._supports_funky_characters = \
943
self._custom_format.supports_funky_characters
944
return self._supports_funky_characters
947
def supports_nesting_repositories(self):
948
if self._supports_nesting_repositories is None:
950
self._supports_nesting_repositories = \
951
self._custom_format.supports_nesting_repositories
952
return self._supports_nesting_repositories
955
def supports_tree_reference(self):
956
if self._supports_tree_reference is None:
958
self._supports_tree_reference = \
959
self._custom_format.supports_tree_reference
960
return self._supports_tree_reference
963
def revision_graph_can_have_wrong_parents(self):
964
if self._revision_graph_can_have_wrong_parents is None:
966
self._revision_graph_can_have_wrong_parents = \
967
self._custom_format.revision_graph_can_have_wrong_parents
968
return self._revision_graph_can_have_wrong_parents
970
def _vfs_initialize(self, a_bzrdir, shared):
971
"""Helper for common code in initialize."""
972
if self._custom_format:
973
# Custom format requested
974
result = self._custom_format.initialize(a_bzrdir, shared=shared)
975
elif self._creating_bzrdir is not None:
976
# Use the format that the repository we were created to back
978
prior_repo = self._creating_bzrdir.open_repository()
979
prior_repo._ensure_real()
980
result = prior_repo._real_repository._format.initialize(
981
a_bzrdir, shared=shared)
983
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
984
# support remote initialization.
985
# We delegate to a real object at this point (as RemoteBzrDir
986
# delegate to the repository format which would lead to infinite
987
# recursion if we just called a_bzrdir.create_repository.
988
a_bzrdir._ensure_real()
989
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
990
if not isinstance(result, RemoteRepository):
991
return self.open(a_bzrdir)
995
def initialize(self, a_bzrdir, shared=False):
996
# Being asked to create on a non RemoteBzrDir:
997
if not isinstance(a_bzrdir, RemoteBzrDir):
998
return self._vfs_initialize(a_bzrdir, shared)
999
medium = a_bzrdir._client._medium
1000
if medium._is_remote_before((1, 13)):
1001
return self._vfs_initialize(a_bzrdir, shared)
1002
# Creating on a remote bzr dir.
1003
# 1) get the network name to use.
1004
if self._custom_format:
1005
network_name = self._custom_format.network_name()
1006
elif self._network_name:
1007
network_name = self._network_name
1009
# Select the current bzrlib default and ask for that.
1010
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1011
reference_format = reference_bzrdir_format.repository_format
1012
network_name = reference_format.network_name()
1013
# 2) try direct creation via RPC
1014
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1015
verb = 'BzrDir.create_repository'
1019
shared_str = 'False'
1021
response = a_bzrdir._call(verb, path, network_name, shared_str)
1022
except errors.UnknownSmartMethod:
1023
# Fallback - use vfs methods
1024
medium._remember_remote_is_before((1, 13))
1025
return self._vfs_initialize(a_bzrdir, shared)
1027
# Turn the response into a RemoteRepository object.
1028
format = response_tuple_to_repo_format(response[1:])
1029
# Used to support creating a real format instance when needed.
1030
format._creating_bzrdir = a_bzrdir
1031
remote_repo = RemoteRepository(a_bzrdir, format)
1032
format._creating_repo = remote_repo
1035
def open(self, a_bzrdir):
1036
if not isinstance(a_bzrdir, RemoteBzrDir):
1037
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1038
return a_bzrdir.open_repository()
1040
def _ensure_real(self):
1041
if self._custom_format is None:
1043
self._custom_format = _mod_repository.network_format_registry.get(
1046
raise errors.UnknownFormatError(kind='repository',
1047
format=self._network_name)
1050
def _fetch_order(self):
1052
return self._custom_format._fetch_order
1055
def _fetch_uses_deltas(self):
1057
return self._custom_format._fetch_uses_deltas
1060
def _fetch_reconcile(self):
1062
return self._custom_format._fetch_reconcile
1064
def get_format_description(self):
1066
return 'Remote: ' + self._custom_format.get_format_description()
1068
def __eq__(self, other):
1069
return self.__class__ is other.__class__
1071
def network_name(self):
1072
if self._network_name:
1073
return self._network_name
1074
self._creating_repo._ensure_real()
1075
return self._creating_repo._real_repository._format.network_name()
1078
def pack_compresses(self):
1080
return self._custom_format.pack_compresses
1083
def _serializer(self):
1085
return self._custom_format._serializer
1088
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1089
lock._RelockDebugMixin):
1090
"""Repository accessed over rpc.
1092
For the moment most operations are performed using local transport-backed
1096
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1097
"""Create a RemoteRepository instance.
1099
:param remote_bzrdir: The bzrdir hosting this repository.
1100
:param format: The RemoteFormat object to use.
1101
:param real_repository: If not None, a local implementation of the
1102
repository logic for the repository, usually accessing the data
1104
:param _client: Private testing parameter - override the smart client
1105
to be used by the repository.
1108
self._real_repository = real_repository
1110
self._real_repository = None
1111
self.bzrdir = remote_bzrdir
1113
self._client = remote_bzrdir._client
1115
self._client = _client
1116
self._format = format
1117
self._lock_mode = None
1118
self._lock_token = None
1119
self._write_group_tokens = None
1120
self._lock_count = 0
1121
self._leave_lock = False
1122
# Cache of revision parents; misses are cached during read locks, and
1123
# write locks when no _real_repository has been set.
1124
self._unstacked_provider = graph.CachingParentsProvider(
1125
get_parent_map=self._get_parent_map_rpc)
1126
self._unstacked_provider.disable_cache()
1128
# These depend on the actual remote format, so force them off for
1129
# maximum compatibility. XXX: In future these should depend on the
1130
# remote repository instance, but this is irrelevant until we perform
1131
# reconcile via an RPC call.
1132
self._reconcile_does_inventory_gc = False
1133
self._reconcile_fixes_text_parents = False
1134
self._reconcile_backsup_inventory = False
1135
self.base = self.bzrdir.transport.base
1136
# Additional places to query for data.
1137
self._fallback_repositories = []
1140
def user_transport(self):
1141
return self.bzrdir.user_transport
1144
def control_transport(self):
1145
# XXX: Normally you shouldn't directly get at the remote repository
1146
# transport, but I'm not sure it's worth making this method
1147
# optional -- mbp 2010-04-21
1148
return self.bzrdir.get_repository_transport(None)
1151
return "%s(%s)" % (self.__class__.__name__, self.base)
1155
def abort_write_group(self, suppress_errors=False):
1156
"""Complete a write group on the decorated repository.
1158
Smart methods perform operations in a single step so this API
1159
is not really applicable except as a compatibility thunk
1160
for older plugins that don't use e.g. the CommitBuilder
1163
:param suppress_errors: see Repository.abort_write_group.
1165
if self._real_repository:
1167
return self._real_repository.abort_write_group(
1168
suppress_errors=suppress_errors)
1169
if not self.is_in_write_group():
1171
mutter('(suppressed) not in write group')
1173
raise errors.BzrError("not in write group")
1174
path = self.bzrdir._path_for_remote_call(self._client)
1176
response = self._call('Repository.abort_write_group', path,
1177
self._lock_token, self._write_group_tokens)
1178
except Exception, exc:
1179
self._write_group = None
1180
if not suppress_errors:
1182
mutter('abort_write_group failed')
1183
log_exception_quietly()
1184
note(gettext('bzr: ERROR (ignored): %s'), exc)
1186
if response != ('ok', ):
1187
raise errors.UnexpectedSmartServerResponse(response)
1188
self._write_group_tokens = None
1191
def chk_bytes(self):
1192
"""Decorate the real repository for now.
1194
In the long term a full blown network facility is needed to avoid
1195
creating a real repository object locally.
1198
return self._real_repository.chk_bytes
1200
def commit_write_group(self):
1201
"""Complete a write group on the decorated repository.
1203
Smart methods perform operations in a single step so this API
1204
is not really applicable except as a compatibility thunk
1205
for older plugins that don't use e.g. the CommitBuilder
1208
if self._real_repository:
1210
return self._real_repository.commit_write_group()
1211
if not self.is_in_write_group():
1212
raise errors.BzrError("not in write group")
1213
path = self.bzrdir._path_for_remote_call(self._client)
1214
response = self._call('Repository.commit_write_group', path,
1215
self._lock_token, self._write_group_tokens)
1216
if response != ('ok', ):
1217
raise errors.UnexpectedSmartServerResponse(response)
1218
self._write_group_tokens = None
1219
# Refresh data after writing to the repository.
1222
def resume_write_group(self, tokens):
1223
if self._real_repository:
1224
return self._real_repository.resume_write_group(tokens)
1225
path = self.bzrdir._path_for_remote_call(self._client)
1227
response = self._call('Repository.check_write_group', path,
1228
self._lock_token, tokens)
1229
except errors.UnknownSmartMethod:
1231
return self._real_repository.resume_write_group(tokens)
1232
if response != ('ok', ):
1233
raise errors.UnexpectedSmartServerResponse(response)
1234
self._write_group_tokens = tokens
1236
def suspend_write_group(self):
1237
if self._real_repository:
1238
return self._real_repository.suspend_write_group()
1239
ret = self._write_group_tokens or []
1240
self._write_group_tokens = None
1243
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1245
return self._real_repository.get_missing_parent_inventories(
1246
check_for_missing_texts=check_for_missing_texts)
1248
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1250
return self._real_repository.get_rev_id_for_revno(
1253
def get_rev_id_for_revno(self, revno, known_pair):
1254
"""See Repository.get_rev_id_for_revno."""
1255
path = self.bzrdir._path_for_remote_call(self._client)
1257
if self._client._medium._is_remote_before((1, 17)):
1258
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1259
response = self._call(
1260
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1261
except errors.UnknownSmartMethod:
1262
self._client._medium._remember_remote_is_before((1, 17))
1263
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1264
if response[0] == 'ok':
1265
return True, response[1]
1266
elif response[0] == 'history-incomplete':
1267
known_pair = response[1:3]
1268
for fallback in self._fallback_repositories:
1269
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1274
# Not found in any fallbacks
1275
return False, known_pair
1277
raise errors.UnexpectedSmartServerResponse(response)
1279
def _ensure_real(self):
1280
"""Ensure that there is a _real_repository set.
1282
Used before calls to self._real_repository.
1284
Note that _ensure_real causes many roundtrips to the server which are
1285
not desirable, and prevents the use of smart one-roundtrip RPC's to
1286
perform complex operations (such as accessing parent data, streaming
1287
revisions etc). Adding calls to _ensure_real should only be done when
1288
bringing up new functionality, adding fallbacks for smart methods that
1289
require a fallback path, and never to replace an existing smart method
1290
invocation. If in doubt chat to the bzr network team.
1292
if self._real_repository is None:
1293
if 'hpssvfs' in debug.debug_flags:
1295
warning('VFS Repository access triggered\n%s',
1296
''.join(traceback.format_stack()))
1297
self._unstacked_provider.missing_keys.clear()
1298
self.bzrdir._ensure_real()
1299
self._set_real_repository(
1300
self.bzrdir._real_bzrdir.open_repository())
1302
def _translate_error(self, err, **context):
1303
self.bzrdir._translate_error(err, repository=self, **context)
1305
def find_text_key_references(self):
1306
"""Find the text key references within the repository.
1308
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1309
to whether they were referred to by the inventory of the
1310
revision_id that they contain. The inventory texts from all present
1311
revision ids are assessed to generate this report.
1314
return self._real_repository.find_text_key_references()
1316
def _generate_text_key_index(self):
1317
"""Generate a new text key index for the repository.
1319
This is an expensive function that will take considerable time to run.
1321
:return: A dict mapping (file_id, revision_id) tuples to a list of
1322
parents, also (file_id, revision_id) tuples.
1325
return self._real_repository._generate_text_key_index()
1327
def _get_revision_graph(self, revision_id):
1328
"""Private method for using with old (< 1.2) servers to fallback."""
1329
if revision_id is None:
1331
elif _mod_revision.is_null(revision_id):
1334
path = self.bzrdir._path_for_remote_call(self._client)
1335
response = self._call_expecting_body(
1336
'Repository.get_revision_graph', path, revision_id)
1337
response_tuple, response_handler = response
1338
if response_tuple[0] != 'ok':
1339
raise errors.UnexpectedSmartServerResponse(response_tuple)
1340
coded = response_handler.read_body_bytes()
1342
# no revisions in this repository!
1344
lines = coded.split('\n')
1347
d = tuple(line.split())
1348
revision_graph[d[0]] = d[1:]
1350
return revision_graph
1352
def _get_sink(self):
1353
"""See Repository._get_sink()."""
1354
return RemoteStreamSink(self)
1356
def _get_source(self, to_format):
1357
"""Return a source for streaming from this repository."""
1358
return RemoteStreamSource(self, to_format)
1361
def get_file_graph(self):
1362
return graph.Graph(self.texts)
1365
def has_revision(self, revision_id):
1366
"""True if this repository has a copy of the revision."""
1367
# Copy of bzrlib.repository.Repository.has_revision
1368
return revision_id in self.has_revisions((revision_id,))
1371
def has_revisions(self, revision_ids):
1372
"""Probe to find out the presence of multiple revisions.
1374
:param revision_ids: An iterable of revision_ids.
1375
:return: A set of the revision_ids that were present.
1377
# Copy of bzrlib.repository.Repository.has_revisions
1378
parent_map = self.get_parent_map(revision_ids)
1379
result = set(parent_map)
1380
if _mod_revision.NULL_REVISION in revision_ids:
1381
result.add(_mod_revision.NULL_REVISION)
1384
def _has_same_fallbacks(self, other_repo):
1385
"""Returns true if the repositories have the same fallbacks."""
1386
# XXX: copied from Repository; it should be unified into a base class
1387
# <https://bugs.launchpad.net/bzr/+bug/401622>
1388
my_fb = self._fallback_repositories
1389
other_fb = other_repo._fallback_repositories
1390
if len(my_fb) != len(other_fb):
1392
for f, g in zip(my_fb, other_fb):
1393
if not f.has_same_location(g):
1397
def has_same_location(self, other):
1398
# TODO: Move to RepositoryBase and unify with the regular Repository
1399
# one; unfortunately the tests rely on slightly different behaviour at
1400
# present -- mbp 20090710
1401
return (self.__class__ is other.__class__ and
1402
self.bzrdir.transport.base == other.bzrdir.transport.base)
1404
def get_graph(self, other_repository=None):
1405
"""Return the graph for this repository format"""
1406
parents_provider = self._make_parents_provider(other_repository)
1407
return graph.Graph(parents_provider)
1410
def get_known_graph_ancestry(self, revision_ids):
1411
"""Return the known graph for a set of revision ids and their ancestors.
1413
st = static_tuple.StaticTuple
1414
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1415
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1416
return graph.GraphThunkIdsToKeys(known_graph)
1418
def gather_stats(self, revid=None, committers=None):
1419
"""See Repository.gather_stats()."""
1420
path = self.bzrdir._path_for_remote_call(self._client)
1421
# revid can be None to indicate no revisions, not just NULL_REVISION
1422
if revid is None or _mod_revision.is_null(revid):
1426
if committers is None or not committers:
1427
fmt_committers = 'no'
1429
fmt_committers = 'yes'
1430
response_tuple, response_handler = self._call_expecting_body(
1431
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1432
if response_tuple[0] != 'ok':
1433
raise errors.UnexpectedSmartServerResponse(response_tuple)
1435
body = response_handler.read_body_bytes()
1437
for line in body.split('\n'):
1440
key, val_text = line.split(':')
1441
if key in ('revisions', 'size', 'committers'):
1442
result[key] = int(val_text)
1443
elif key in ('firstrev', 'latestrev'):
1444
values = val_text.split(' ')[1:]
1445
result[key] = (float(values[0]), long(values[1]))
1449
def find_branches(self, using=False):
1450
"""See Repository.find_branches()."""
1451
# should be an API call to the server.
1453
return self._real_repository.find_branches(using=using)
1455
def get_physical_lock_status(self):
1456
"""See Repository.get_physical_lock_status()."""
1457
path = self.bzrdir._path_for_remote_call(self._client)
1459
response = self._call('Repository.get_physical_lock_status', path)
1460
except errors.UnknownSmartMethod:
1462
return self._real_repository.get_physical_lock_status()
1463
if response[0] not in ('yes', 'no'):
1464
raise errors.UnexpectedSmartServerResponse(response)
1465
return (response[0] == 'yes')
1467
def is_in_write_group(self):
1468
"""Return True if there is an open write group.
1470
write groups are only applicable locally for the smart server..
1472
if self._write_group_tokens is not None:
1474
if self._real_repository:
1475
return self._real_repository.is_in_write_group()
1477
def is_locked(self):
1478
return self._lock_count >= 1
1480
def is_shared(self):
1481
"""See Repository.is_shared()."""
1482
path = self.bzrdir._path_for_remote_call(self._client)
1483
response = self._call('Repository.is_shared', path)
1484
if response[0] not in ('yes', 'no'):
1485
raise SmartProtocolError('unexpected response code %s' % (response,))
1486
return response[0] == 'yes'
1488
def is_write_locked(self):
1489
return self._lock_mode == 'w'
1491
def _warn_if_deprecated(self, branch=None):
1492
# If we have a real repository, the check will be done there, if we
1493
# don't the check will be done remotely.
1496
def lock_read(self):
1497
"""Lock the repository for read operations.
1499
:return: A bzrlib.lock.LogicalLockResult.
1501
# wrong eventually - want a local lock cache context
1502
if not self._lock_mode:
1503
self._note_lock('r')
1504
self._lock_mode = 'r'
1505
self._lock_count = 1
1506
self._unstacked_provider.enable_cache(cache_misses=True)
1507
if self._real_repository is not None:
1508
self._real_repository.lock_read()
1509
for repo in self._fallback_repositories:
1512
self._lock_count += 1
1513
return lock.LogicalLockResult(self.unlock)
1515
def _remote_lock_write(self, token):
1516
path = self.bzrdir._path_for_remote_call(self._client)
1519
err_context = {'token': token}
1520
response = self._call('Repository.lock_write', path, token,
1522
if response[0] == 'ok':
1523
ok, token = response
1526
raise errors.UnexpectedSmartServerResponse(response)
1528
def lock_write(self, token=None, _skip_rpc=False):
1529
if not self._lock_mode:
1530
self._note_lock('w')
1532
if self._lock_token is not None:
1533
if token != self._lock_token:
1534
raise errors.TokenMismatch(token, self._lock_token)
1535
self._lock_token = token
1537
self._lock_token = self._remote_lock_write(token)
1538
# if self._lock_token is None, then this is something like packs or
1539
# svn where we don't get to lock the repo, or a weave style repository
1540
# where we cannot lock it over the wire and attempts to do so will
1542
if self._real_repository is not None:
1543
self._real_repository.lock_write(token=self._lock_token)
1544
if token is not None:
1545
self._leave_lock = True
1547
self._leave_lock = False
1548
self._lock_mode = 'w'
1549
self._lock_count = 1
1550
cache_misses = self._real_repository is None
1551
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1552
for repo in self._fallback_repositories:
1553
# Writes don't affect fallback repos
1555
elif self._lock_mode == 'r':
1556
raise errors.ReadOnlyError(self)
1558
self._lock_count += 1
1559
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1561
def leave_lock_in_place(self):
1562
if not self._lock_token:
1563
raise NotImplementedError(self.leave_lock_in_place)
1564
self._leave_lock = True
1566
def dont_leave_lock_in_place(self):
1567
if not self._lock_token:
1568
raise NotImplementedError(self.dont_leave_lock_in_place)
1569
self._leave_lock = False
1571
def _set_real_repository(self, repository):
1572
"""Set the _real_repository for this repository.
1574
:param repository: The repository to fallback to for non-hpss
1575
implemented operations.
1577
if self._real_repository is not None:
1578
# Replacing an already set real repository.
1579
# We cannot do this [currently] if the repository is locked -
1580
# synchronised state might be lost.
1581
if self.is_locked():
1582
raise AssertionError('_real_repository is already set')
1583
if isinstance(repository, RemoteRepository):
1584
raise AssertionError()
1585
self._real_repository = repository
1586
# three code paths happen here:
1587
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1588
# up stacking. In this case self._fallback_repositories is [], and the
1589
# real repo is already setup. Preserve the real repo and
1590
# RemoteRepository.add_fallback_repository will avoid adding
1592
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1593
# ensure_real is triggered from a branch, the real repository to
1594
# set already has a matching list with separate instances, but
1595
# as they are also RemoteRepositories we don't worry about making the
1596
# lists be identical.
1597
# 3) new servers, RemoteRepository.ensure_real is triggered before
1598
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1599
# and need to populate it.
1600
if (self._fallback_repositories and
1601
len(self._real_repository._fallback_repositories) !=
1602
len(self._fallback_repositories)):
1603
if len(self._real_repository._fallback_repositories):
1604
raise AssertionError(
1605
"cannot cleanly remove existing _fallback_repositories")
1606
for fb in self._fallback_repositories:
1607
self._real_repository.add_fallback_repository(fb)
1608
if self._lock_mode == 'w':
1609
# if we are already locked, the real repository must be able to
1610
# acquire the lock with our token.
1611
self._real_repository.lock_write(self._lock_token)
1612
elif self._lock_mode == 'r':
1613
self._real_repository.lock_read()
1614
if self._write_group_tokens is not None:
1615
# if we are already in a write group, resume it
1616
self._real_repository.resume_write_group(self._write_group_tokens)
1617
self._write_group_tokens = None
1619
def start_write_group(self):
1620
"""Start a write group on the decorated repository.
1622
Smart methods perform operations in a single step so this API
1623
is not really applicable except as a compatibility thunk
1624
for older plugins that don't use e.g. the CommitBuilder
1627
if self._real_repository:
1629
return self._real_repository.start_write_group()
1630
if not self.is_write_locked():
1631
raise errors.NotWriteLocked(self)
1632
if self._write_group_tokens is not None:
1633
raise errors.BzrError('already in a write group')
1634
path = self.bzrdir._path_for_remote_call(self._client)
1636
response = self._call('Repository.start_write_group', path,
1638
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1640
return self._real_repository.start_write_group()
1641
if response[0] != 'ok':
1642
raise errors.UnexpectedSmartServerResponse(response)
1643
self._write_group_tokens = response[1]
1645
def _unlock(self, token):
1646
path = self.bzrdir._path_for_remote_call(self._client)
1648
# with no token the remote repository is not persistently locked.
1650
err_context = {'token': token}
1651
response = self._call('Repository.unlock', path, token,
1653
if response == ('ok',):
1656
raise errors.UnexpectedSmartServerResponse(response)
1658
@only_raises(errors.LockNotHeld, errors.LockBroken)
1660
if not self._lock_count:
1661
return lock.cant_unlock_not_held(self)
1662
self._lock_count -= 1
1663
if self._lock_count > 0:
1665
self._unstacked_provider.disable_cache()
1666
old_mode = self._lock_mode
1667
self._lock_mode = None
1669
# The real repository is responsible at present for raising an
1670
# exception if it's in an unfinished write group. However, it
1671
# normally will *not* actually remove the lock from disk - that's
1672
# done by the server on receiving the Repository.unlock call.
1673
# This is just to let the _real_repository stay up to date.
1674
if self._real_repository is not None:
1675
self._real_repository.unlock()
1676
elif self._write_group_tokens is not None:
1677
self.abort_write_group()
1679
# The rpc-level lock should be released even if there was a
1680
# problem releasing the vfs-based lock.
1682
# Only write-locked repositories need to make a remote method
1683
# call to perform the unlock.
1684
old_token = self._lock_token
1685
self._lock_token = None
1686
if not self._leave_lock:
1687
self._unlock(old_token)
1688
# Fallbacks are always 'lock_read()' so we don't pay attention to
1690
for repo in self._fallback_repositories:
1693
def break_lock(self):
1694
# should hand off to the network
1695
path = self.bzrdir._path_for_remote_call(self._client)
1697
response = self._call("Repository.break_lock", path)
1698
except errors.UnknownSmartMethod:
1700
return self._real_repository.break_lock()
1701
if response != ('ok',):
1702
raise errors.UnexpectedSmartServerResponse(response)
1704
def _get_tarball(self, compression):
1705
"""Return a TemporaryFile containing a repository tarball.
1707
Returns None if the server does not support sending tarballs.
1710
path = self.bzrdir._path_for_remote_call(self._client)
1712
response, protocol = self._call_expecting_body(
1713
'Repository.tarball', path, compression)
1714
except errors.UnknownSmartMethod:
1715
protocol.cancel_read_body()
1717
if response[0] == 'ok':
1718
# Extract the tarball and return it
1719
t = tempfile.NamedTemporaryFile()
1720
# TODO: rpc layer should read directly into it...
1721
t.write(protocol.read_body_bytes())
1724
raise errors.UnexpectedSmartServerResponse(response)
1727
def sprout(self, to_bzrdir, revision_id=None):
1728
"""Create a descendent repository for new development.
1730
Unlike clone, this does not copy the settings of the repository.
1732
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1733
dest_repo.fetch(self, revision_id=revision_id)
1736
def _create_sprouting_repo(self, a_bzrdir, shared):
1737
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1738
# use target default format.
1739
dest_repo = a_bzrdir.create_repository()
1741
# Most control formats need the repository to be specifically
1742
# created, but on some old all-in-one formats it's not needed
1744
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1745
except errors.UninitializableFormat:
1746
dest_repo = a_bzrdir.open_repository()
1749
### These methods are just thin shims to the VFS object for now.
1752
def revision_tree(self, revision_id):
1753
revision_id = _mod_revision.ensure_null(revision_id)
1754
if revision_id == _mod_revision.NULL_REVISION:
1755
return InventoryRevisionTree(self,
1756
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1758
return list(self.revision_trees([revision_id]))[0]
1760
def get_serializer_format(self):
1761
path = self.bzrdir._path_for_remote_call(self._client)
1763
response = self._call('VersionedFileRepository.get_serializer_format',
1765
except errors.UnknownSmartMethod:
1767
return self._real_repository.get_serializer_format()
1768
if response[0] != 'ok':
1769
raise errors.UnexpectedSmartServerResponse(response)
1772
def get_commit_builder(self, branch, parents, config, timestamp=None,
1773
timezone=None, committer=None, revprops=None,
1774
revision_id=None, lossy=False):
1775
"""Obtain a CommitBuilder for this repository.
1777
:param branch: Branch to commit to.
1778
:param parents: Revision ids of the parents of the new revision.
1779
:param config: Configuration to use.
1780
:param timestamp: Optional timestamp recorded for commit.
1781
:param timezone: Optional timezone for timestamp.
1782
:param committer: Optional committer to set for commit.
1783
:param revprops: Optional dictionary of revision properties.
1784
:param revision_id: Optional revision id.
1785
:param lossy: Whether to discard data that can not be natively
1786
represented, when pushing to a foreign VCS
1788
if self._fallback_repositories and not self._format.supports_chks:
1789
raise errors.BzrError("Cannot commit directly to a stacked branch"
1790
" in pre-2a formats. See "
1791
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1792
if self._format.rich_root_data:
1793
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1795
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1796
result = commit_builder_kls(self, parents, config,
1797
timestamp, timezone, committer, revprops, revision_id,
1799
self.start_write_group()
1802
def add_fallback_repository(self, repository):
1803
"""Add a repository to use for looking up data not held locally.
1805
:param repository: A repository.
1807
if not self._format.supports_external_lookups:
1808
raise errors.UnstackableRepositoryFormat(
1809
self._format.network_name(), self.base)
1810
# We need to accumulate additional repositories here, to pass them in
1813
# Make the check before we lock: this raises an exception.
1814
self._check_fallback_repository(repository)
1815
if self.is_locked():
1816
# We will call fallback.unlock() when we transition to the unlocked
1817
# state, so always add a lock here. If a caller passes us a locked
1818
# repository, they are responsible for unlocking it later.
1819
repository.lock_read()
1820
self._fallback_repositories.append(repository)
1821
# If self._real_repository was parameterised already (e.g. because a
1822
# _real_branch had its get_stacked_on_url method called), then the
1823
# repository to be added may already be in the _real_repositories list.
1824
if self._real_repository is not None:
1825
fallback_locations = [repo.user_url for repo in
1826
self._real_repository._fallback_repositories]
1827
if repository.user_url not in fallback_locations:
1828
self._real_repository.add_fallback_repository(repository)
1830
def _check_fallback_repository(self, repository):
1831
"""Check that this repository can fallback to repository safely.
1833
Raise an error if not.
1835
:param repository: A repository to fallback to.
1837
return _mod_repository.InterRepository._assert_same_model(
1840
def add_inventory(self, revid, inv, parents):
1842
return self._real_repository.add_inventory(revid, inv, parents)
1844
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1845
parents, basis_inv=None, propagate_caches=False):
1847
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1848
delta, new_revision_id, parents, basis_inv=basis_inv,
1849
propagate_caches=propagate_caches)
1851
def add_revision(self, revision_id, rev, inv=None):
1852
_mod_revision.check_not_reserved_id(revision_id)
1853
key = (revision_id,)
1854
# check inventory present
1855
if not self.inventories.get_parent_map([key]):
1857
raise errors.WeaveRevisionNotPresent(revision_id,
1860
# yes, this is not suitable for adding with ghosts.
1861
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1864
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1865
self._add_revision(rev)
1867
def _add_revision(self, rev):
1868
if self._real_repository is not None:
1869
return self._real_repository._add_revision(rev)
1870
text = self._serializer.write_revision_to_string(rev)
1871
key = (rev.revision_id,)
1872
parents = tuple((parent,) for parent in rev.parent_ids)
1873
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1874
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1875
self._format, self._write_group_tokens)
1878
def get_inventory(self, revision_id):
1879
return list(self.iter_inventories([revision_id]))[0]
1881
def _iter_inventories_rpc(self, revision_ids, ordering):
1882
if ordering is None:
1883
ordering = 'unordered'
1884
path = self.bzrdir._path_for_remote_call(self._client)
1885
body = "\n".join(revision_ids)
1886
response_tuple, response_handler = (
1887
self._call_with_body_bytes_expecting_body(
1888
"VersionedFileRepository.get_inventories",
1889
(path, ordering), body))
1890
if response_tuple[0] != "ok":
1891
raise errors.UnexpectedSmartServerResponse(response_tuple)
1892
deserializer = inventory_delta.InventoryDeltaDeserializer()
1893
byte_stream = response_handler.read_streamed_body()
1894
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1896
# no results whatsoever
1898
src_format, stream = decoded
1899
if src_format.network_name() != self._format.network_name():
1900
raise AssertionError(
1901
"Mismatched RemoteRepository and stream src %r, %r" % (
1902
src_format.network_name(), self._format.network_name()))
1903
# ignore the src format, it's not really relevant
1904
prev_inv = Inventory(root_id=None,
1905
revision_id=_mod_revision.NULL_REVISION)
1906
# there should be just one substream, with inventory deltas
1907
substream_kind, substream = stream.next()
1908
if substream_kind != "inventory-deltas":
1909
raise AssertionError(
1910
"Unexpected stream %r received" % substream_kind)
1911
for record in substream:
1912
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1913
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1914
if parent_id != prev_inv.revision_id:
1915
raise AssertionError("invalid base %r != %r" % (parent_id,
1916
prev_inv.revision_id))
1917
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1918
yield inv, inv.revision_id
1921
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1923
return self._real_repository._iter_inventories(revision_ids, ordering)
1925
def iter_inventories(self, revision_ids, ordering=None):
1926
"""Get many inventories by revision_ids.
1928
This will buffer some or all of the texts used in constructing the
1929
inventories in memory, but will only parse a single inventory at a
1932
:param revision_ids: The expected revision ids of the inventories.
1933
:param ordering: optional ordering, e.g. 'topological'. If not
1934
specified, the order of revision_ids will be preserved (by
1935
buffering if necessary).
1936
:return: An iterator of inventories.
1938
if ((None in revision_ids)
1939
or (_mod_revision.NULL_REVISION in revision_ids)):
1940
raise ValueError('cannot get null revision inventory')
1941
for inv, revid in self._iter_inventories(revision_ids, ordering):
1943
raise errors.NoSuchRevision(self, revid)
1946
def _iter_inventories(self, revision_ids, ordering=None):
1947
if len(revision_ids) == 0:
1949
missing = set(revision_ids)
1950
if ordering is None:
1951
order_as_requested = True
1953
order = list(revision_ids)
1955
next_revid = order.pop()
1957
order_as_requested = False
1958
if ordering != 'unordered' and self._fallback_repositories:
1959
raise ValueError('unsupported ordering %r' % ordering)
1960
iter_inv_fns = [self._iter_inventories_rpc] + [
1961
fallback._iter_inventories for fallback in
1962
self._fallback_repositories]
1964
for iter_inv in iter_inv_fns:
1965
request = [revid for revid in revision_ids if revid in missing]
1966
for inv, revid in iter_inv(request, ordering):
1969
missing.remove(inv.revision_id)
1970
if ordering != 'unordered':
1974
if order_as_requested:
1975
# Yield as many results as we can while preserving order.
1976
while next_revid in invs:
1977
inv = invs.pop(next_revid)
1978
yield inv, inv.revision_id
1980
next_revid = order.pop()
1982
# We still want to fully consume the stream, just
1983
# in case it is not actually finished at this point
1986
except errors.UnknownSmartMethod:
1987
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
1991
if order_as_requested:
1992
if next_revid is not None:
1993
yield None, next_revid
1996
yield invs.get(revid), revid
1999
yield None, missing.pop()
2002
def get_revision(self, revision_id):
2003
return self.get_revisions([revision_id])[0]
2005
def get_transaction(self):
2007
return self._real_repository.get_transaction()
2010
def clone(self, a_bzrdir, revision_id=None):
2011
dest_repo = self._create_sprouting_repo(
2012
a_bzrdir, shared=self.is_shared())
2013
self.copy_content_into(dest_repo, revision_id)
2016
def make_working_trees(self):
2017
"""See Repository.make_working_trees"""
2018
path = self.bzrdir._path_for_remote_call(self._client)
2020
response = self._call('Repository.make_working_trees', path)
2021
except errors.UnknownSmartMethod:
2023
return self._real_repository.make_working_trees()
2024
if response[0] not in ('yes', 'no'):
2025
raise SmartProtocolError('unexpected response code %s' % (response,))
2026
return response[0] == 'yes'
2028
def refresh_data(self):
2029
"""Re-read any data needed to synchronise with disk.
2031
This method is intended to be called after another repository instance
2032
(such as one used by a smart server) has inserted data into the
2033
repository. On all repositories this will work outside of write groups.
2034
Some repository formats (pack and newer for bzrlib native formats)
2035
support refresh_data inside write groups. If called inside a write
2036
group on a repository that does not support refreshing in a write group
2037
IsInWriteGroupError will be raised.
2039
if self._real_repository is not None:
2040
self._real_repository.refresh_data()
2041
# Refresh the parents cache for this object
2042
self._unstacked_provider.disable_cache()
2043
self._unstacked_provider.enable_cache()
2045
def revision_ids_to_search_result(self, result_set):
2046
"""Convert a set of revision ids to a graph SearchResult."""
2047
result_parents = set()
2048
for parents in self.get_graph().get_parent_map(
2049
result_set).itervalues():
2050
result_parents.update(parents)
2051
included_keys = result_set.intersection(result_parents)
2052
start_keys = result_set.difference(included_keys)
2053
exclude_keys = result_parents.difference(result_set)
2054
result = vf_search.SearchResult(start_keys, exclude_keys,
2055
len(result_set), result_set)
2059
def search_missing_revision_ids(self, other,
2060
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2061
find_ghosts=True, revision_ids=None, if_present_ids=None,
2063
"""Return the revision ids that other has that this does not.
2065
These are returned in topological order.
2067
revision_id: only return revision ids included by revision_id.
2069
if symbol_versioning.deprecated_passed(revision_id):
2070
symbol_versioning.warn(
2071
'search_missing_revision_ids(revision_id=...) was '
2072
'deprecated in 2.4. Use revision_ids=[...] instead.',
2073
DeprecationWarning, stacklevel=2)
2074
if revision_ids is not None:
2075
raise AssertionError(
2076
'revision_ids is mutually exclusive with revision_id')
2077
if revision_id is not None:
2078
revision_ids = [revision_id]
2079
inter_repo = _mod_repository.InterRepository.get(other, self)
2080
return inter_repo.search_missing_revision_ids(
2081
find_ghosts=find_ghosts, revision_ids=revision_ids,
2082
if_present_ids=if_present_ids, limit=limit)
2084
def fetch(self, source, revision_id=None, find_ghosts=False,
2086
# No base implementation to use as RemoteRepository is not a subclass
2087
# of Repository; so this is a copy of Repository.fetch().
2088
if fetch_spec is not None and revision_id is not None:
2089
raise AssertionError(
2090
"fetch_spec and revision_id are mutually exclusive.")
2091
if self.is_in_write_group():
2092
raise errors.InternalBzrError(
2093
"May not fetch while in a write group.")
2094
# fast path same-url fetch operations
2095
if (self.has_same_location(source)
2096
and fetch_spec is None
2097
and self._has_same_fallbacks(source)):
2098
# check that last_revision is in 'from' and then return a
2100
if (revision_id is not None and
2101
not _mod_revision.is_null(revision_id)):
2102
self.get_revision(revision_id)
2104
# if there is no specific appropriate InterRepository, this will get
2105
# the InterRepository base class, which raises an
2106
# IncompatibleRepositories when asked to fetch.
2107
inter = _mod_repository.InterRepository.get(source, self)
2108
if (fetch_spec is not None and
2109
not getattr(inter, "supports_fetch_spec", False)):
2110
raise errors.UnsupportedOperation(
2111
"fetch_spec not supported for %r" % inter)
2112
return inter.fetch(revision_id=revision_id,
2113
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2115
def create_bundle(self, target, base, fileobj, format=None):
2117
self._real_repository.create_bundle(target, base, fileobj, format)
2120
@symbol_versioning.deprecated_method(
2121
symbol_versioning.deprecated_in((2, 4, 0)))
2122
def get_ancestry(self, revision_id, topo_sorted=True):
2124
return self._real_repository.get_ancestry(revision_id, topo_sorted)
2126
def fileids_altered_by_revision_ids(self, revision_ids):
2128
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2130
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2132
return self._real_repository._get_versioned_file_checker(
2133
revisions, revision_versions_cache)
2135
def _iter_files_bytes_rpc(self, desired_files, absent):
2136
path = self.bzrdir._path_for_remote_call(self._client)
2139
for (file_id, revid, identifier) in desired_files:
2140
lines.append("%s\0%s" % (
2141
osutils.safe_file_id(file_id),
2142
osutils.safe_revision_id(revid)))
2143
identifiers.append(identifier)
2144
(response_tuple, response_handler) = (
2145
self._call_with_body_bytes_expecting_body(
2146
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2147
if response_tuple != ('ok', ):
2148
response_handler.cancel_read_body()
2149
raise errors.UnexpectedSmartServerResponse(response_tuple)
2150
byte_stream = response_handler.read_streamed_body()
2151
def decompress_stream(start, byte_stream, unused):
2152
decompressor = zlib.decompressobj()
2153
yield decompressor.decompress(start)
2154
while decompressor.unused_data == "":
2156
data = byte_stream.next()
2157
except StopIteration:
2159
yield decompressor.decompress(data)
2160
yield decompressor.flush()
2161
unused.append(decompressor.unused_data)
2164
while not "\n" in unused:
2165
unused += byte_stream.next()
2166
header, rest = unused.split("\n", 1)
2167
args = header.split("\0")
2168
if args[0] == "absent":
2169
absent[identifiers[int(args[3])]] = (args[1], args[2])
2172
elif args[0] == "ok":
2175
raise errors.UnexpectedSmartServerResponse(args)
2177
yield (identifiers[idx],
2178
decompress_stream(rest, byte_stream, unused_chunks))
2179
unused = "".join(unused_chunks)
2181
def iter_files_bytes(self, desired_files):
2182
"""See Repository.iter_file_bytes.
2186
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2187
desired_files, absent):
2188
yield identifier, bytes_iterator
2189
for fallback in self._fallback_repositories:
2192
desired_files = [(key[0], key[1], identifier) for
2193
(identifier, key) in absent.iteritems()]
2194
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2195
del absent[identifier]
2196
yield identifier, bytes_iterator
2198
# There may be more missing items, but raise an exception
2200
missing_identifier = absent.keys()[0]
2201
missing_key = absent[missing_identifier]
2202
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2203
file_id=missing_key[0])
2204
except errors.UnknownSmartMethod:
2206
for (identifier, bytes_iterator) in (
2207
self._real_repository.iter_files_bytes(desired_files)):
2208
yield identifier, bytes_iterator
2210
def get_cached_parent_map(self, revision_ids):
2211
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2212
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2214
def get_parent_map(self, revision_ids):
2215
"""See bzrlib.Graph.get_parent_map()."""
2216
return self._make_parents_provider().get_parent_map(revision_ids)
2218
def _get_parent_map_rpc(self, keys):
2219
"""Helper for get_parent_map that performs the RPC."""
2220
medium = self._client._medium
2221
if medium._is_remote_before((1, 2)):
2222
# We already found out that the server can't understand
2223
# Repository.get_parent_map requests, so just fetch the whole
2226
# Note that this reads the whole graph, when only some keys are
2227
# wanted. On this old server there's no way (?) to get them all
2228
# in one go, and the user probably will have seen a warning about
2229
# the server being old anyhow.
2230
rg = self._get_revision_graph(None)
2231
# There is an API discrepancy between get_parent_map and
2232
# get_revision_graph. Specifically, a "key:()" pair in
2233
# get_revision_graph just means a node has no parents. For
2234
# "get_parent_map" it means the node is a ghost. So fix up the
2235
# graph to correct this.
2236
# https://bugs.launchpad.net/bzr/+bug/214894
2237
# There is one other "bug" which is that ghosts in
2238
# get_revision_graph() are not returned at all. But we won't worry
2239
# about that for now.
2240
for node_id, parent_ids in rg.iteritems():
2241
if parent_ids == ():
2242
rg[node_id] = (NULL_REVISION,)
2243
rg[NULL_REVISION] = ()
2248
raise ValueError('get_parent_map(None) is not valid')
2249
if NULL_REVISION in keys:
2250
keys.discard(NULL_REVISION)
2251
found_parents = {NULL_REVISION:()}
2253
return found_parents
2256
# TODO(Needs analysis): We could assume that the keys being requested
2257
# from get_parent_map are in a breadth first search, so typically they
2258
# will all be depth N from some common parent, and we don't have to
2259
# have the server iterate from the root parent, but rather from the
2260
# keys we're searching; and just tell the server the keyspace we
2261
# already have; but this may be more traffic again.
2263
# Transform self._parents_map into a search request recipe.
2264
# TODO: Manage this incrementally to avoid covering the same path
2265
# repeatedly. (The server will have to on each request, but the less
2266
# work done the better).
2268
# Negative caching notes:
2269
# new server sends missing when a request including the revid
2270
# 'include-missing:' is present in the request.
2271
# missing keys are serialised as missing:X, and we then call
2272
# provider.note_missing(X) for-all X
2273
parents_map = self._unstacked_provider.get_cached_map()
2274
if parents_map is None:
2275
# Repository is not locked, so there's no cache.
2277
if _DEFAULT_SEARCH_DEPTH <= 0:
2278
(start_set, stop_keys,
2279
key_count) = vf_search.search_result_from_parent_map(
2280
parents_map, self._unstacked_provider.missing_keys)
2282
(start_set, stop_keys,
2283
key_count) = vf_search.limited_search_result_from_parent_map(
2284
parents_map, self._unstacked_provider.missing_keys,
2285
keys, depth=_DEFAULT_SEARCH_DEPTH)
2286
recipe = ('manual', start_set, stop_keys, key_count)
2287
body = self._serialise_search_recipe(recipe)
2288
path = self.bzrdir._path_for_remote_call(self._client)
2290
if type(key) is not str:
2292
"key %r not a plain string" % (key,))
2293
verb = 'Repository.get_parent_map'
2294
args = (path, 'include-missing:') + tuple(keys)
2296
response = self._call_with_body_bytes_expecting_body(
2298
except errors.UnknownSmartMethod:
2299
# Server does not support this method, so get the whole graph.
2300
# Worse, we have to force a disconnection, because the server now
2301
# doesn't realise it has a body on the wire to consume, so the
2302
# only way to recover is to abandon the connection.
2304
'Server is too old for fast get_parent_map, reconnecting. '
2305
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2307
# To avoid having to disconnect repeatedly, we keep track of the
2308
# fact the server doesn't understand remote methods added in 1.2.
2309
medium._remember_remote_is_before((1, 2))
2310
# Recurse just once and we should use the fallback code.
2311
return self._get_parent_map_rpc(keys)
2312
response_tuple, response_handler = response
2313
if response_tuple[0] not in ['ok']:
2314
response_handler.cancel_read_body()
2315
raise errors.UnexpectedSmartServerResponse(response_tuple)
2316
if response_tuple[0] == 'ok':
2317
coded = bz2.decompress(response_handler.read_body_bytes())
2319
# no revisions found
2321
lines = coded.split('\n')
2324
d = tuple(line.split())
2326
revision_graph[d[0]] = d[1:]
2329
if d[0].startswith('missing:'):
2331
self._unstacked_provider.note_missing_key(revid)
2333
# no parents - so give the Graph result
2335
revision_graph[d[0]] = (NULL_REVISION,)
2336
return revision_graph
2339
def get_signature_text(self, revision_id):
2340
path = self.bzrdir._path_for_remote_call(self._client)
2342
response_tuple, response_handler = self._call_expecting_body(
2343
'Repository.get_revision_signature_text', path, revision_id)
2344
except errors.UnknownSmartMethod:
2346
return self._real_repository.get_signature_text(revision_id)
2347
except errors.NoSuchRevision, err:
2348
for fallback in self._fallback_repositories:
2350
return fallback.get_signature_text(revision_id)
2351
except errors.NoSuchRevision:
2355
if response_tuple[0] != 'ok':
2356
raise errors.UnexpectedSmartServerResponse(response_tuple)
2357
return response_handler.read_body_bytes()
2360
def _get_inventory_xml(self, revision_id):
2361
# This call is used by older working tree formats,
2362
# which stored a serialized basis inventory.
2364
return self._real_repository._get_inventory_xml(revision_id)
2367
def reconcile(self, other=None, thorough=False):
2368
from bzrlib.reconcile import RepoReconciler
2369
path = self.bzrdir._path_for_remote_call(self._client)
2371
response, handler = self._call_expecting_body(
2372
'Repository.reconcile', path, self._lock_token)
2373
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2375
return self._real_repository.reconcile(other=other, thorough=thorough)
2376
if response != ('ok', ):
2377
raise errors.UnexpectedSmartServerResponse(response)
2378
body = handler.read_body_bytes()
2379
result = RepoReconciler(self)
2380
for line in body.split('\n'):
2383
key, val_text = line.split(':')
2384
if key == "garbage_inventories":
2385
result.garbage_inventories = int(val_text)
2386
elif key == "inconsistent_parents":
2387
result.inconsistent_parents = int(val_text)
2389
mutter("unknown reconcile key %r" % key)
2392
def all_revision_ids(self):
2393
path = self.bzrdir._path_for_remote_call(self._client)
2395
response_tuple, response_handler = self._call_expecting_body(
2396
"Repository.all_revision_ids", path)
2397
except errors.UnknownSmartMethod:
2399
return self._real_repository.all_revision_ids()
2400
if response_tuple != ("ok", ):
2401
raise errors.UnexpectedSmartServerResponse(response_tuple)
2402
revids = set(response_handler.read_body_bytes().splitlines())
2403
for fallback in self._fallback_repositories:
2404
revids.update(set(fallback.all_revision_ids()))
2407
def _filtered_revision_trees(self, revision_ids, file_ids):
2408
"""Return Tree for a revision on this branch with only some files.
2410
:param revision_ids: a sequence of revision-ids;
2411
a revision-id may not be None or 'null:'
2412
:param file_ids: if not None, the result is filtered
2413
so that only those file-ids, their parents and their
2414
children are included.
2416
inventories = self.iter_inventories(revision_ids)
2417
for inv in inventories:
2418
# Should we introduce a FilteredRevisionTree class rather
2419
# than pre-filter the inventory here?
2420
filtered_inv = inv.filter(file_ids)
2421
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2424
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2425
medium = self._client._medium
2426
if medium._is_remote_before((1, 2)):
2428
for delta in self._real_repository.get_deltas_for_revisions(
2429
revisions, specific_fileids):
2432
# Get the revision-ids of interest
2433
required_trees = set()
2434
for revision in revisions:
2435
required_trees.add(revision.revision_id)
2436
required_trees.update(revision.parent_ids[:1])
2438
# Get the matching filtered trees. Note that it's more
2439
# efficient to pass filtered trees to changes_from() rather
2440
# than doing the filtering afterwards. changes_from() could
2441
# arguably do the filtering itself but it's path-based, not
2442
# file-id based, so filtering before or afterwards is
2444
if specific_fileids is None:
2445
trees = dict((t.get_revision_id(), t) for
2446
t in self.revision_trees(required_trees))
2448
trees = dict((t.get_revision_id(), t) for
2449
t in self._filtered_revision_trees(required_trees,
2452
# Calculate the deltas
2453
for revision in revisions:
2454
if not revision.parent_ids:
2455
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2457
old_tree = trees[revision.parent_ids[0]]
2458
yield trees[revision.revision_id].changes_from(old_tree)
2461
def get_revision_delta(self, revision_id, specific_fileids=None):
2462
r = self.get_revision(revision_id)
2463
return list(self.get_deltas_for_revisions([r],
2464
specific_fileids=specific_fileids))[0]
2467
def revision_trees(self, revision_ids):
2468
inventories = self.iter_inventories(revision_ids)
2469
for inv in inventories:
2470
yield InventoryRevisionTree(self, inv, inv.revision_id)
2473
def get_revision_reconcile(self, revision_id):
2475
return self._real_repository.get_revision_reconcile(revision_id)
2478
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2480
return self._real_repository.check(revision_ids=revision_ids,
2481
callback_refs=callback_refs, check_repo=check_repo)
2483
def copy_content_into(self, destination, revision_id=None):
2484
"""Make a complete copy of the content in self into destination.
2486
This is a destructive operation! Do not use it on existing
2489
interrepo = _mod_repository.InterRepository.get(self, destination)
2490
return interrepo.copy_content(revision_id)
2492
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2493
# get a tarball of the remote repository, and copy from that into the
2496
# TODO: Maybe a progress bar while streaming the tarball?
2497
note(gettext("Copying repository content as tarball..."))
2498
tar_file = self._get_tarball('bz2')
2499
if tar_file is None:
2501
destination = to_bzrdir.create_repository()
2503
tar = tarfile.open('repository', fileobj=tar_file,
2505
tmpdir = osutils.mkdtemp()
2507
_extract_tar(tar, tmpdir)
2508
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2509
tmp_repo = tmp_bzrdir.open_repository()
2510
tmp_repo.copy_content_into(destination, revision_id)
2512
osutils.rmtree(tmpdir)
2516
# TODO: Suggestion from john: using external tar is much faster than
2517
# python's tarfile library, but it may not work on windows.
2520
def inventories(self):
2521
"""Decorate the real repository for now.
2523
In the long term a full blown network facility is needed to
2524
avoid creating a real repository object locally.
2527
return self._real_repository.inventories
2530
def pack(self, hint=None, clean_obsolete_packs=False):
2531
"""Compress the data within the repository.
2536
body = "".join([l+"\n" for l in hint])
2537
path = self.bzrdir._path_for_remote_call(self._client)
2539
response, handler = self._call_with_body_bytes_expecting_body(
2540
'Repository.pack', (path, self._lock_token,
2541
str(clean_obsolete_packs)), body)
2542
except errors.UnknownSmartMethod:
2544
return self._real_repository.pack(hint=hint,
2545
clean_obsolete_packs=clean_obsolete_packs)
2546
handler.cancel_read_body()
2547
if response != ('ok', ):
2548
raise errors.UnexpectedSmartServerResponse(response)
2551
def revisions(self):
2552
"""Decorate the real repository for now.
2554
In the long term a full blown network facility is needed.
2557
return self._real_repository.revisions
2559
def set_make_working_trees(self, new_value):
2561
new_value_str = "True"
2563
new_value_str = "False"
2564
path = self.bzrdir._path_for_remote_call(self._client)
2566
response = self._call(
2567
'Repository.set_make_working_trees', path, new_value_str)
2568
except errors.UnknownSmartMethod:
2570
self._real_repository.set_make_working_trees(new_value)
2572
if response[0] != 'ok':
2573
raise errors.UnexpectedSmartServerResponse(response)
2576
def signatures(self):
2577
"""Decorate the real repository for now.
2579
In the long term a full blown network facility is needed to avoid
2580
creating a real repository object locally.
2583
return self._real_repository.signatures
2586
def sign_revision(self, revision_id, gpg_strategy):
2587
testament = _mod_testament.Testament.from_revision(self, revision_id)
2588
plaintext = testament.as_short_text()
2589
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2593
"""Decorate the real repository for now.
2595
In the long term a full blown network facility is needed to avoid
2596
creating a real repository object locally.
2599
return self._real_repository.texts
2601
def _iter_revisions_rpc(self, revision_ids):
2602
body = "\n".join(revision_ids)
2603
path = self.bzrdir._path_for_remote_call(self._client)
2604
response_tuple, response_handler = (
2605
self._call_with_body_bytes_expecting_body(
2606
"Repository.iter_revisions", (path, ), body))
2607
if response_tuple[0] != "ok":
2608
raise errors.UnexpectedSmartServerResponse(response_tuple)
2609
serializer_format = response_tuple[1]
2610
serializer = serializer_format_registry.get(serializer_format)
2611
byte_stream = response_handler.read_streamed_body()
2612
decompressor = zlib.decompressobj()
2614
for bytes in byte_stream:
2615
chunks.append(decompressor.decompress(bytes))
2616
if decompressor.unused_data != "":
2617
chunks.append(decompressor.flush())
2618
yield serializer.read_revision_from_string("".join(chunks))
2619
unused = decompressor.unused_data
2620
decompressor = zlib.decompressobj()
2621
chunks = [decompressor.decompress(unused)]
2622
chunks.append(decompressor.flush())
2623
text = "".join(chunks)
2625
yield serializer.read_revision_from_string("".join(chunks))
2628
def get_revisions(self, revision_ids):
2629
if revision_ids is None:
2630
revision_ids = self.all_revision_ids()
2632
for rev_id in revision_ids:
2633
if not rev_id or not isinstance(rev_id, basestring):
2634
raise errors.InvalidRevisionId(
2635
revision_id=rev_id, branch=self)
2637
missing = set(revision_ids)
2639
for rev in self._iter_revisions_rpc(revision_ids):
2640
missing.remove(rev.revision_id)
2641
revs[rev.revision_id] = rev
2642
except errors.UnknownSmartMethod:
2644
return self._real_repository.get_revisions(revision_ids)
2645
for fallback in self._fallback_repositories:
2648
for revid in list(missing):
2649
# XXX JRV 2011-11-20: It would be nice if there was a
2650
# public method on Repository that could be used to query
2651
# for revision objects *without* failing completely if one
2652
# was missing. There is VersionedFileRepository._iter_revisions,
2653
# but unfortunately that's private and not provided by
2654
# all repository implementations.
2656
revs[revid] = fallback.get_revision(revid)
2657
except errors.NoSuchRevision:
2660
missing.remove(revid)
2662
raise errors.NoSuchRevision(self, list(missing)[0])
2663
return [revs[revid] for revid in revision_ids]
2665
def supports_rich_root(self):
2666
return self._format.rich_root_data
2668
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2669
def iter_reverse_revision_history(self, revision_id):
2671
return self._real_repository.iter_reverse_revision_history(revision_id)
2674
def _serializer(self):
2675
return self._format._serializer
2678
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2679
signature = gpg_strategy.sign(plaintext)
2680
self.add_signature_text(revision_id, signature)
2682
def add_signature_text(self, revision_id, signature):
2683
if self._real_repository:
2684
# If there is a real repository the write group will
2685
# be in the real repository as well, so use that:
2687
return self._real_repository.add_signature_text(
2688
revision_id, signature)
2689
path = self.bzrdir._path_for_remote_call(self._client)
2690
response, handler = self._call_with_body_bytes_expecting_body(
2691
'Repository.add_signature_text', (path, self._lock_token,
2692
revision_id) + tuple(self._write_group_tokens), signature)
2693
handler.cancel_read_body()
2695
if response[0] != 'ok':
2696
raise errors.UnexpectedSmartServerResponse(response)
2697
self._write_group_tokens = response[1:]
2699
def has_signature_for_revision_id(self, revision_id):
2700
path = self.bzrdir._path_for_remote_call(self._client)
2702
response = self._call('Repository.has_signature_for_revision_id',
2704
except errors.UnknownSmartMethod:
2706
return self._real_repository.has_signature_for_revision_id(
2708
if response[0] not in ('yes', 'no'):
2709
raise SmartProtocolError('unexpected response code %s' % (response,))
2710
if response[0] == 'yes':
2712
for fallback in self._fallback_repositories:
2713
if fallback.has_signature_for_revision_id(revision_id):
2718
def verify_revision_signature(self, revision_id, gpg_strategy):
2719
if not self.has_signature_for_revision_id(revision_id):
2720
return gpg.SIGNATURE_NOT_SIGNED, None
2721
signature = self.get_signature_text(revision_id)
2723
testament = _mod_testament.Testament.from_revision(self, revision_id)
2724
plaintext = testament.as_short_text()
2726
return gpg_strategy.verify(signature, plaintext)
2728
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2730
return self._real_repository.item_keys_introduced_by(revision_ids,
2731
_files_pb=_files_pb)
2733
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2735
return self._real_repository._find_inconsistent_revision_parents(
2738
def _check_for_inconsistent_revision_parents(self):
2740
return self._real_repository._check_for_inconsistent_revision_parents()
2742
def _make_parents_provider(self, other=None):
2743
providers = [self._unstacked_provider]
2744
if other is not None:
2745
providers.insert(0, other)
2746
return graph.StackedParentsProvider(_LazyListJoin(
2747
providers, self._fallback_repositories))
2749
def _serialise_search_recipe(self, recipe):
2750
"""Serialise a graph search recipe.
2752
:param recipe: A search recipe (start, stop, count).
2753
:return: Serialised bytes.
2755
start_keys = ' '.join(recipe[1])
2756
stop_keys = ' '.join(recipe[2])
2757
count = str(recipe[3])
2758
return '\n'.join((start_keys, stop_keys, count))
2760
def _serialise_search_result(self, search_result):
2761
parts = search_result.get_network_struct()
2762
return '\n'.join(parts)
2765
path = self.bzrdir._path_for_remote_call(self._client)
2767
response = self._call('PackRepository.autopack', path)
2768
except errors.UnknownSmartMethod:
2770
self._real_repository._pack_collection.autopack()
2773
if response[0] != 'ok':
2774
raise errors.UnexpectedSmartServerResponse(response)
2777
class RemoteStreamSink(vf_repository.StreamSink):
2779
def _insert_real(self, stream, src_format, resume_tokens):
2780
self.target_repo._ensure_real()
2781
sink = self.target_repo._real_repository._get_sink()
2782
result = sink.insert_stream(stream, src_format, resume_tokens)
2784
self.target_repo.autopack()
2787
def insert_stream(self, stream, src_format, resume_tokens):
2788
target = self.target_repo
2789
target._unstacked_provider.missing_keys.clear()
2790
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2791
if target._lock_token:
2792
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2793
lock_args = (target._lock_token or '',)
2795
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2797
client = target._client
2798
medium = client._medium
2799
path = target.bzrdir._path_for_remote_call(client)
2800
# Probe for the verb to use with an empty stream before sending the
2801
# real stream to it. We do this both to avoid the risk of sending a
2802
# large request that is then rejected, and because we don't want to
2803
# implement a way to buffer, rewind, or restart the stream.
2805
for verb, required_version in candidate_calls:
2806
if medium._is_remote_before(required_version):
2809
# We've already done the probing (and set _is_remote_before) on
2810
# a previous insert.
2813
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2815
response = client.call_with_body_stream(
2816
(verb, path, '') + lock_args, byte_stream)
2817
except errors.UnknownSmartMethod:
2818
medium._remember_remote_is_before(required_version)
2824
return self._insert_real(stream, src_format, resume_tokens)
2825
self._last_inv_record = None
2826
self._last_substream = None
2827
if required_version < (1, 19):
2828
# Remote side doesn't support inventory deltas. Wrap the stream to
2829
# make sure we don't send any. If the stream contains inventory
2830
# deltas we'll interrupt the smart insert_stream request and
2832
stream = self._stop_stream_if_inventory_delta(stream)
2833
byte_stream = smart_repo._stream_to_byte_stream(
2835
resume_tokens = ' '.join(resume_tokens)
2836
response = client.call_with_body_stream(
2837
(verb, path, resume_tokens) + lock_args, byte_stream)
2838
if response[0][0] not in ('ok', 'missing-basis'):
2839
raise errors.UnexpectedSmartServerResponse(response)
2840
if self._last_substream is not None:
2841
# The stream included an inventory-delta record, but the remote
2842
# side isn't new enough to support them. So we need to send the
2843
# rest of the stream via VFS.
2844
self.target_repo.refresh_data()
2845
return self._resume_stream_with_vfs(response, src_format)
2846
if response[0][0] == 'missing-basis':
2847
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2848
resume_tokens = tokens
2849
return resume_tokens, set(missing_keys)
2851
self.target_repo.refresh_data()
2854
def _resume_stream_with_vfs(self, response, src_format):
2855
"""Resume sending a stream via VFS, first resending the record and
2856
substream that couldn't be sent via an insert_stream verb.
2858
if response[0][0] == 'missing-basis':
2859
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2860
# Ignore missing_keys, we haven't finished inserting yet
2863
def resume_substream():
2864
# Yield the substream that was interrupted.
2865
for record in self._last_substream:
2867
self._last_substream = None
2868
def resume_stream():
2869
# Finish sending the interrupted substream
2870
yield ('inventory-deltas', resume_substream())
2871
# Then simply continue sending the rest of the stream.
2872
for substream_kind, substream in self._last_stream:
2873
yield substream_kind, substream
2874
return self._insert_real(resume_stream(), src_format, tokens)
2876
def _stop_stream_if_inventory_delta(self, stream):
2877
"""Normally this just lets the original stream pass-through unchanged.
2879
However if any 'inventory-deltas' substream occurs it will stop
2880
streaming, and store the interrupted substream and stream in
2881
self._last_substream and self._last_stream so that the stream can be
2882
resumed by _resume_stream_with_vfs.
2885
stream_iter = iter(stream)
2886
for substream_kind, substream in stream_iter:
2887
if substream_kind == 'inventory-deltas':
2888
self._last_substream = substream
2889
self._last_stream = stream_iter
2892
yield substream_kind, substream
2895
class RemoteStreamSource(vf_repository.StreamSource):
2896
"""Stream data from a remote server."""
2898
def get_stream(self, search):
2899
if (self.from_repository._fallback_repositories and
2900
self.to_format._fetch_order == 'topological'):
2901
return self._real_stream(self.from_repository, search)
2904
repos = [self.from_repository]
2910
repos.extend(repo._fallback_repositories)
2911
sources.append(repo)
2912
return self.missing_parents_chain(search, sources)
2914
def get_stream_for_missing_keys(self, missing_keys):
2915
self.from_repository._ensure_real()
2916
real_repo = self.from_repository._real_repository
2917
real_source = real_repo._get_source(self.to_format)
2918
return real_source.get_stream_for_missing_keys(missing_keys)
2920
def _real_stream(self, repo, search):
2921
"""Get a stream for search from repo.
2923
This never called RemoteStreamSource.get_stream, and is a helper
2924
for RemoteStreamSource._get_stream to allow getting a stream
2925
reliably whether fallback back because of old servers or trying
2926
to stream from a non-RemoteRepository (which the stacked support
2929
source = repo._get_source(self.to_format)
2930
if isinstance(source, RemoteStreamSource):
2932
source = repo._real_repository._get_source(self.to_format)
2933
return source.get_stream(search)
2935
def _get_stream(self, repo, search):
2936
"""Core worker to get a stream from repo for search.
2938
This is used by both get_stream and the stacking support logic. It
2939
deliberately gets a stream for repo which does not need to be
2940
self.from_repository. In the event that repo is not Remote, or
2941
cannot do a smart stream, a fallback is made to the generic
2942
repository._get_stream() interface, via self._real_stream.
2944
In the event of stacking, streams from _get_stream will not
2945
contain all the data for search - this is normal (see get_stream).
2947
:param repo: A repository.
2948
:param search: A search.
2950
# Fallbacks may be non-smart
2951
if not isinstance(repo, RemoteRepository):
2952
return self._real_stream(repo, search)
2953
client = repo._client
2954
medium = client._medium
2955
path = repo.bzrdir._path_for_remote_call(client)
2956
search_bytes = repo._serialise_search_result(search)
2957
args = (path, self.to_format.network_name())
2959
('Repository.get_stream_1.19', (1, 19)),
2960
('Repository.get_stream', (1, 13))]
2963
for verb, version in candidate_verbs:
2964
if medium._is_remote_before(version):
2967
response = repo._call_with_body_bytes_expecting_body(
2968
verb, args, search_bytes)
2969
except errors.UnknownSmartMethod:
2970
medium._remember_remote_is_before(version)
2971
except errors.UnknownErrorFromSmartServer, e:
2972
if isinstance(search, vf_search.EverythingResult):
2973
error_verb = e.error_from_smart_server.error_verb
2974
if error_verb == 'BadSearch':
2975
# Pre-2.4 servers don't support this sort of search.
2976
# XXX: perhaps falling back to VFS on BadSearch is a
2977
# good idea in general? It might provide a little bit
2978
# of protection against client-side bugs.
2979
medium._remember_remote_is_before((2, 4))
2983
response_tuple, response_handler = response
2987
return self._real_stream(repo, search)
2988
if response_tuple[0] != 'ok':
2989
raise errors.UnexpectedSmartServerResponse(response_tuple)
2990
byte_stream = response_handler.read_streamed_body()
2991
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2992
self._record_counter)
2993
if src_format.network_name() != repo._format.network_name():
2994
raise AssertionError(
2995
"Mismatched RemoteRepository and stream src %r, %r" % (
2996
src_format.network_name(), repo._format.network_name()))
2999
def missing_parents_chain(self, search, sources):
3000
"""Chain multiple streams together to handle stacking.
3002
:param search: The overall search to satisfy with streams.
3003
:param sources: A list of Repository objects to query.
3005
self.from_serialiser = self.from_repository._format._serializer
3006
self.seen_revs = set()
3007
self.referenced_revs = set()
3008
# If there are heads in the search, or the key count is > 0, we are not
3010
while not search.is_empty() and len(sources) > 1:
3011
source = sources.pop(0)
3012
stream = self._get_stream(source, search)
3013
for kind, substream in stream:
3014
if kind != 'revisions':
3015
yield kind, substream
3017
yield kind, self.missing_parents_rev_handler(substream)
3018
search = search.refine(self.seen_revs, self.referenced_revs)
3019
self.seen_revs = set()
3020
self.referenced_revs = set()
3021
if not search.is_empty():
3022
for kind, stream in self._get_stream(sources[0], search):
3025
def missing_parents_rev_handler(self, substream):
3026
for content in substream:
3027
revision_bytes = content.get_bytes_as('fulltext')
3028
revision = self.from_serialiser.read_revision_from_string(
3030
self.seen_revs.add(content.key[-1])
3031
self.referenced_revs.update(revision.parent_ids)
3035
class RemoteBranchLockableFiles(LockableFiles):
3036
"""A 'LockableFiles' implementation that talks to a smart server.
3038
This is not a public interface class.
3041
def __init__(self, bzrdir, _client):
3042
self.bzrdir = bzrdir
3043
self._client = _client
3044
self._need_find_modes = True
3045
LockableFiles.__init__(
3046
self, bzrdir.get_branch_transport(None),
3047
'lock', lockdir.LockDir)
3049
def _find_modes(self):
3050
# RemoteBranches don't let the client set the mode of control files.
3051
self._dir_mode = None
3052
self._file_mode = None
3055
class RemoteBranchFormat(branch.BranchFormat):
3057
def __init__(self, network_name=None):
3058
super(RemoteBranchFormat, self).__init__()
3059
self._matchingbzrdir = RemoteBzrDirFormat()
3060
self._matchingbzrdir.set_branch_format(self)
3061
self._custom_format = None
3062
self._network_name = network_name
3064
def __eq__(self, other):
3065
return (isinstance(other, RemoteBranchFormat) and
3066
self.__dict__ == other.__dict__)
3068
def _ensure_real(self):
3069
if self._custom_format is None:
3071
self._custom_format = branch.network_format_registry.get(
3074
raise errors.UnknownFormatError(kind='branch',
3075
format=self._network_name)
3077
def get_format_description(self):
3079
return 'Remote: ' + self._custom_format.get_format_description()
3081
def network_name(self):
3082
return self._network_name
3084
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3085
return a_bzrdir.open_branch(name=name,
3086
ignore_fallbacks=ignore_fallbacks)
3088
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3089
# Initialisation when using a local bzrdir object, or a non-vfs init
3090
# method is not available on the server.
3091
# self._custom_format is always set - the start of initialize ensures
3093
if isinstance(a_bzrdir, RemoteBzrDir):
3094
a_bzrdir._ensure_real()
3095
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3096
name, append_revisions_only=append_revisions_only)
3098
# We assume the bzrdir is parameterised; it may not be.
3099
result = self._custom_format.initialize(a_bzrdir, name,
3100
append_revisions_only=append_revisions_only)
3101
if (isinstance(a_bzrdir, RemoteBzrDir) and
3102
not isinstance(result, RemoteBranch)):
3103
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3107
def initialize(self, a_bzrdir, name=None, repository=None,
3108
append_revisions_only=None):
3110
name = a_bzrdir._get_selected_branch()
3111
# 1) get the network name to use.
3112
if self._custom_format:
3113
network_name = self._custom_format.network_name()
3115
# Select the current bzrlib default and ask for that.
3116
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3117
reference_format = reference_bzrdir_format.get_branch_format()
3118
self._custom_format = reference_format
3119
network_name = reference_format.network_name()
3120
# Being asked to create on a non RemoteBzrDir:
3121
if not isinstance(a_bzrdir, RemoteBzrDir):
3122
return self._vfs_initialize(a_bzrdir, name=name,
3123
append_revisions_only=append_revisions_only)
3124
medium = a_bzrdir._client._medium
3125
if medium._is_remote_before((1, 13)):
3126
return self._vfs_initialize(a_bzrdir, name=name,
3127
append_revisions_only=append_revisions_only)
3128
# Creating on a remote bzr dir.
3129
# 2) try direct creation via RPC
3130
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3132
# XXX JRV20100304: Support creating colocated branches
3133
raise errors.NoColocatedBranchSupport(self)
3134
verb = 'BzrDir.create_branch'
3136
response = a_bzrdir._call(verb, path, network_name)
3137
except errors.UnknownSmartMethod:
3138
# Fallback - use vfs methods
3139
medium._remember_remote_is_before((1, 13))
3140
return self._vfs_initialize(a_bzrdir, name=name,
3141
append_revisions_only=append_revisions_only)
3142
if response[0] != 'ok':
3143
raise errors.UnexpectedSmartServerResponse(response)
3144
# Turn the response into a RemoteRepository object.
3145
format = RemoteBranchFormat(network_name=response[1])
3146
repo_format = response_tuple_to_repo_format(response[3:])
3147
repo_path = response[2]
3148
if repository is not None:
3149
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3150
url_diff = urlutils.relative_url(repository.user_url,
3153
raise AssertionError(
3154
'repository.user_url %r does not match URL from server '
3155
'response (%r + %r)'
3156
% (repository.user_url, a_bzrdir.user_url, repo_path))
3157
remote_repo = repository
3160
repo_bzrdir = a_bzrdir
3162
repo_bzrdir = RemoteBzrDir(
3163
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3165
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3166
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3167
format=format, setup_stacking=False, name=name)
3168
if append_revisions_only:
3169
remote_branch.set_append_revisions_only(append_revisions_only)
3170
# XXX: We know this is a new branch, so it must have revno 0, revid
3171
# NULL_REVISION. Creating the branch locked would make this be unable
3172
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3173
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3174
return remote_branch
3176
def make_tags(self, branch):
3178
return self._custom_format.make_tags(branch)
3180
def supports_tags(self):
3181
# Remote branches might support tags, but we won't know until we
3182
# access the real remote branch.
3184
return self._custom_format.supports_tags()
3186
def supports_stacking(self):
3188
return self._custom_format.supports_stacking()
3190
def supports_set_append_revisions_only(self):
3192
return self._custom_format.supports_set_append_revisions_only()
3194
def _use_default_local_heads_to_fetch(self):
3195
# If the branch format is a metadir format *and* its heads_to_fetch
3196
# implementation is not overridden vs the base class, we can use the
3197
# base class logic rather than use the heads_to_fetch RPC. This is
3198
# usually cheaper in terms of net round trips, as the last-revision and
3199
# tags info fetched is cached and would be fetched anyway.
3201
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3202
branch_class = self._custom_format._branch_class()
3203
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3204
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3209
class RemoteBranchStore(_mod_config.IniFileStore):
3210
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3212
Note that this is specific to bzr-based formats.
3215
def __init__(self, branch):
3216
super(RemoteBranchStore, self).__init__()
3217
self.branch = branch
3219
self._real_store = None
3221
def lock_write(self, token=None):
3222
return self.branch.lock_write(token)
3225
return self.branch.unlock()
3229
# We need to be able to override the undecorated implementation
3230
self.save_without_locking()
3232
def save_without_locking(self):
3233
super(RemoteBranchStore, self).save()
3235
def external_url(self):
3236
return self.branch.user_url
3238
def _load_content(self):
3239
path = self.branch._remote_path()
3241
response, handler = self.branch._call_expecting_body(
3242
'Branch.get_config_file', path)
3243
except errors.UnknownSmartMethod:
3245
return self._real_store._load_content()
3246
if len(response) and response[0] != 'ok':
3247
raise errors.UnexpectedSmartServerResponse(response)
3248
return handler.read_body_bytes()
3250
def _save_content(self, content):
3251
path = self.branch._remote_path()
3253
response, handler = self.branch._call_with_body_bytes_expecting_body(
3254
'Branch.put_config_file', (path,
3255
self.branch._lock_token, self.branch._repo_lock_token),
3257
except errors.UnknownSmartMethod:
3259
return self._real_store._save_content(content)
3260
handler.cancel_read_body()
3261
if response != ('ok', ):
3262
raise errors.UnexpectedSmartServerResponse(response)
3264
def _ensure_real(self):
3265
self.branch._ensure_real()
3266
if self._real_store is None:
3267
self._real_store = _mod_config.BranchStore(self.branch)
3270
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3271
"""Branch stored on a server accessed by HPSS RPC.
3273
At the moment most operations are mapped down to simple file operations.
3276
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3277
_client=None, format=None, setup_stacking=True, name=None,
3278
possible_transports=None):
3279
"""Create a RemoteBranch instance.
3281
:param real_branch: An optional local implementation of the branch
3282
format, usually accessing the data via the VFS.
3283
:param _client: Private parameter for testing.
3284
:param format: A RemoteBranchFormat object, None to create one
3285
automatically. If supplied it should have a network_name already
3287
:param setup_stacking: If True make an RPC call to determine the
3288
stacked (or not) status of the branch. If False assume the branch
3290
:param name: Colocated branch name
3292
# We intentionally don't call the parent class's __init__, because it
3293
# will try to assign to self.tags, which is a property in this subclass.
3294
# And the parent's __init__ doesn't do much anyway.
3295
self.bzrdir = remote_bzrdir
3296
if _client is not None:
3297
self._client = _client
3299
self._client = remote_bzrdir._client
3300
self.repository = remote_repository
3301
if real_branch is not None:
3302
self._real_branch = real_branch
3303
# Give the remote repository the matching real repo.
3304
real_repo = self._real_branch.repository
3305
if isinstance(real_repo, RemoteRepository):
3306
real_repo._ensure_real()
3307
real_repo = real_repo._real_repository
3308
self.repository._set_real_repository(real_repo)
3309
# Give the branch the remote repository to let fast-pathing happen.
3310
self._real_branch.repository = self.repository
3312
self._real_branch = None
3313
# Fill out expected attributes of branch for bzrlib API users.
3314
self._clear_cached_state()
3315
# TODO: deprecate self.base in favor of user_url
3316
self.base = self.bzrdir.user_url
3318
self._control_files = None
3319
self._lock_mode = None
3320
self._lock_token = None
3321
self._repo_lock_token = None
3322
self._lock_count = 0
3323
self._leave_lock = False
3324
# Setup a format: note that we cannot call _ensure_real until all the
3325
# attributes above are set: This code cannot be moved higher up in this
3328
self._format = RemoteBranchFormat()
3329
if real_branch is not None:
3330
self._format._network_name = \
3331
self._real_branch._format.network_name()
3333
self._format = format
3334
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3335
# branch.open_branch method.
3336
self._real_ignore_fallbacks = not setup_stacking
3337
if not self._format._network_name:
3338
# Did not get from open_branchV2 - old server.
3340
self._format._network_name = \
3341
self._real_branch._format.network_name()
3342
self.tags = self._format.make_tags(self)
3343
# The base class init is not called, so we duplicate this:
3344
hooks = branch.Branch.hooks['open']
3347
self._is_stacked = False
3349
self._setup_stacking(possible_transports)
3351
def _setup_stacking(self, possible_transports):
3352
# configure stacking into the remote repository, by reading it from
3355
fallback_url = self.get_stacked_on_url()
3356
except (errors.NotStacked, errors.UnstackableBranchFormat,
3357
errors.UnstackableRepositoryFormat), e:
3359
self._is_stacked = True
3360
if possible_transports is None:
3361
possible_transports = []
3363
possible_transports = list(possible_transports)
3364
possible_transports.append(self.bzrdir.root_transport)
3365
self._activate_fallback_location(fallback_url,
3366
possible_transports=possible_transports)
3368
def _get_config(self):
3369
return RemoteBranchConfig(self)
3371
def _get_config_store(self):
3372
return RemoteBranchStore(self)
3374
def _get_real_transport(self):
3375
# if we try vfs access, return the real branch's vfs transport
3377
return self._real_branch._transport
3379
_transport = property(_get_real_transport)
3382
return "%s(%s)" % (self.__class__.__name__, self.base)
3386
def _ensure_real(self):
3387
"""Ensure that there is a _real_branch set.
3389
Used before calls to self._real_branch.
3391
if self._real_branch is None:
3392
if not vfs.vfs_enabled():
3393
raise AssertionError('smart server vfs must be enabled '
3394
'to use vfs implementation')
3395
self.bzrdir._ensure_real()
3396
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3397
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3398
if self.repository._real_repository is None:
3399
# Give the remote repository the matching real repo.
3400
real_repo = self._real_branch.repository
3401
if isinstance(real_repo, RemoteRepository):
3402
real_repo._ensure_real()
3403
real_repo = real_repo._real_repository
3404
self.repository._set_real_repository(real_repo)
3405
# Give the real branch the remote repository to let fast-pathing
3407
self._real_branch.repository = self.repository
3408
if self._lock_mode == 'r':
3409
self._real_branch.lock_read()
3410
elif self._lock_mode == 'w':
3411
self._real_branch.lock_write(token=self._lock_token)
3413
def _translate_error(self, err, **context):
3414
self.repository._translate_error(err, branch=self, **context)
3416
def _clear_cached_state(self):
3417
super(RemoteBranch, self)._clear_cached_state()
3418
if self._real_branch is not None:
3419
self._real_branch._clear_cached_state()
3421
def _clear_cached_state_of_remote_branch_only(self):
3422
"""Like _clear_cached_state, but doesn't clear the cache of
3425
This is useful when falling back to calling a method of
3426
self._real_branch that changes state. In that case the underlying
3427
branch changes, so we need to invalidate this RemoteBranch's cache of
3428
it. However, there's no need to invalidate the _real_branch's cache
3429
too, in fact doing so might harm performance.
3431
super(RemoteBranch, self)._clear_cached_state()
3434
def control_files(self):
3435
# Defer actually creating RemoteBranchLockableFiles until its needed,
3436
# because it triggers an _ensure_real that we otherwise might not need.
3437
if self._control_files is None:
3438
self._control_files = RemoteBranchLockableFiles(
3439
self.bzrdir, self._client)
3440
return self._control_files
3442
def get_physical_lock_status(self):
3443
"""See Branch.get_physical_lock_status()."""
3445
response = self._client.call('Branch.get_physical_lock_status',
3446
self._remote_path())
3447
except errors.UnknownSmartMethod:
3449
return self._real_branch.get_physical_lock_status()
3450
if response[0] not in ('yes', 'no'):
3451
raise errors.UnexpectedSmartServerResponse(response)
3452
return (response[0] == 'yes')
3454
def get_stacked_on_url(self):
3455
"""Get the URL this branch is stacked against.
3457
:raises NotStacked: If the branch is not stacked.
3458
:raises UnstackableBranchFormat: If the branch does not support
3460
:raises UnstackableRepositoryFormat: If the repository does not support
3464
# there may not be a repository yet, so we can't use
3465
# self._translate_error, so we can't use self._call either.
3466
response = self._client.call('Branch.get_stacked_on_url',
3467
self._remote_path())
3468
except errors.ErrorFromSmartServer, err:
3469
# there may not be a repository yet, so we can't call through
3470
# its _translate_error
3471
_translate_error(err, branch=self)
3472
except errors.UnknownSmartMethod, err:
3474
return self._real_branch.get_stacked_on_url()
3475
if response[0] != 'ok':
3476
raise errors.UnexpectedSmartServerResponse(response)
3479
def set_stacked_on_url(self, url):
3480
branch.Branch.set_stacked_on_url(self, url)
3482
self._is_stacked = False
3484
self._is_stacked = True
3486
def _vfs_get_tags_bytes(self):
3488
return self._real_branch._get_tags_bytes()
3491
def _get_tags_bytes(self):
3492
if self._tags_bytes is None:
3493
self._tags_bytes = self._get_tags_bytes_via_hpss()
3494
return self._tags_bytes
3496
def _get_tags_bytes_via_hpss(self):
3497
medium = self._client._medium
3498
if medium._is_remote_before((1, 13)):
3499
return self._vfs_get_tags_bytes()
3501
response = self._call('Branch.get_tags_bytes', self._remote_path())
3502
except errors.UnknownSmartMethod:
3503
medium._remember_remote_is_before((1, 13))
3504
return self._vfs_get_tags_bytes()
3507
def _vfs_set_tags_bytes(self, bytes):
3509
return self._real_branch._set_tags_bytes(bytes)
3511
def _set_tags_bytes(self, bytes):
3512
if self.is_locked():
3513
self._tags_bytes = bytes
3514
medium = self._client._medium
3515
if medium._is_remote_before((1, 18)):
3516
self._vfs_set_tags_bytes(bytes)
3520
self._remote_path(), self._lock_token, self._repo_lock_token)
3521
response = self._call_with_body_bytes(
3522
'Branch.set_tags_bytes', args, bytes)
3523
except errors.UnknownSmartMethod:
3524
medium._remember_remote_is_before((1, 18))
3525
self._vfs_set_tags_bytes(bytes)
3527
def lock_read(self):
3528
"""Lock the branch for read operations.
3530
:return: A bzrlib.lock.LogicalLockResult.
3532
self.repository.lock_read()
3533
if not self._lock_mode:
3534
self._note_lock('r')
3535
self._lock_mode = 'r'
3536
self._lock_count = 1
3537
if self._real_branch is not None:
3538
self._real_branch.lock_read()
3540
self._lock_count += 1
3541
return lock.LogicalLockResult(self.unlock)
3543
def _remote_lock_write(self, token):
3545
branch_token = repo_token = ''
3547
branch_token = token
3548
repo_token = self.repository.lock_write().repository_token
3549
self.repository.unlock()
3550
err_context = {'token': token}
3552
response = self._call(
3553
'Branch.lock_write', self._remote_path(), branch_token,
3554
repo_token or '', **err_context)
3555
except errors.LockContention, e:
3556
# The LockContention from the server doesn't have any
3557
# information about the lock_url. We re-raise LockContention
3558
# with valid lock_url.
3559
raise errors.LockContention('(remote lock)',
3560
self.repository.base.split('.bzr/')[0])
3561
if response[0] != 'ok':
3562
raise errors.UnexpectedSmartServerResponse(response)
3563
ok, branch_token, repo_token = response
3564
return branch_token, repo_token
3566
def lock_write(self, token=None):
3567
if not self._lock_mode:
3568
self._note_lock('w')
3569
# Lock the branch and repo in one remote call.
3570
remote_tokens = self._remote_lock_write(token)
3571
self._lock_token, self._repo_lock_token = remote_tokens
3572
if not self._lock_token:
3573
raise SmartProtocolError('Remote server did not return a token!')
3574
# Tell the self.repository object that it is locked.
3575
self.repository.lock_write(
3576
self._repo_lock_token, _skip_rpc=True)
3578
if self._real_branch is not None:
3579
self._real_branch.lock_write(token=self._lock_token)
3580
if token is not None:
3581
self._leave_lock = True
3583
self._leave_lock = False
3584
self._lock_mode = 'w'
3585
self._lock_count = 1
3586
elif self._lock_mode == 'r':
3587
raise errors.ReadOnlyError(self)
3589
if token is not None:
3590
# A token was given to lock_write, and we're relocking, so
3591
# check that the given token actually matches the one we
3593
if token != self._lock_token:
3594
raise errors.TokenMismatch(token, self._lock_token)
3595
self._lock_count += 1
3596
# Re-lock the repository too.
3597
self.repository.lock_write(self._repo_lock_token)
3598
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3600
def _unlock(self, branch_token, repo_token):
3601
err_context = {'token': str((branch_token, repo_token))}
3602
response = self._call(
3603
'Branch.unlock', self._remote_path(), branch_token,
3604
repo_token or '', **err_context)
3605
if response == ('ok',):
3607
raise errors.UnexpectedSmartServerResponse(response)
3609
@only_raises(errors.LockNotHeld, errors.LockBroken)
3612
self._lock_count -= 1
3613
if not self._lock_count:
3614
self._clear_cached_state()
3615
mode = self._lock_mode
3616
self._lock_mode = None
3617
if self._real_branch is not None:
3618
if (not self._leave_lock and mode == 'w' and
3619
self._repo_lock_token):
3620
# If this RemoteBranch will remove the physical lock
3621
# for the repository, make sure the _real_branch
3622
# doesn't do it first. (Because the _real_branch's
3623
# repository is set to be the RemoteRepository.)
3624
self._real_branch.repository.leave_lock_in_place()
3625
self._real_branch.unlock()
3627
# Only write-locked branched need to make a remote method
3628
# call to perform the unlock.
3630
if not self._lock_token:
3631
raise AssertionError('Locked, but no token!')
3632
branch_token = self._lock_token
3633
repo_token = self._repo_lock_token
3634
self._lock_token = None
3635
self._repo_lock_token = None
3636
if not self._leave_lock:
3637
self._unlock(branch_token, repo_token)
3639
self.repository.unlock()
3641
def break_lock(self):
3643
response = self._call(
3644
'Branch.break_lock', self._remote_path())
3645
except errors.UnknownSmartMethod:
3647
return self._real_branch.break_lock()
3648
if response != ('ok',):
3649
raise errors.UnexpectedSmartServerResponse(response)
3651
def leave_lock_in_place(self):
3652
if not self._lock_token:
3653
raise NotImplementedError(self.leave_lock_in_place)
3654
self._leave_lock = True
3656
def dont_leave_lock_in_place(self):
3657
if not self._lock_token:
3658
raise NotImplementedError(self.dont_leave_lock_in_place)
3659
self._leave_lock = False
3662
def get_rev_id(self, revno, history=None):
3664
return _mod_revision.NULL_REVISION
3665
last_revision_info = self.last_revision_info()
3666
ok, result = self.repository.get_rev_id_for_revno(
3667
revno, last_revision_info)
3670
missing_parent = result[1]
3671
# Either the revision named by the server is missing, or its parent
3672
# is. Call get_parent_map to determine which, so that we report a
3674
parent_map = self.repository.get_parent_map([missing_parent])
3675
if missing_parent in parent_map:
3676
missing_parent = parent_map[missing_parent]
3677
raise errors.RevisionNotPresent(missing_parent, self.repository)
3679
def _read_last_revision_info(self):
3680
response = self._call('Branch.last_revision_info', self._remote_path())
3681
if response[0] != 'ok':
3682
raise SmartProtocolError('unexpected response code %s' % (response,))
3683
revno = int(response[1])
3684
last_revision = response[2]
3685
return (revno, last_revision)
3687
def _gen_revision_history(self):
3688
"""See Branch._gen_revision_history()."""
3689
if self._is_stacked:
3691
return self._real_branch._gen_revision_history()
3692
response_tuple, response_handler = self._call_expecting_body(
3693
'Branch.revision_history', self._remote_path())
3694
if response_tuple[0] != 'ok':
3695
raise errors.UnexpectedSmartServerResponse(response_tuple)
3696
result = response_handler.read_body_bytes().split('\x00')
3701
def _remote_path(self):
3702
return self.bzrdir._path_for_remote_call(self._client)
3704
def _set_last_revision_descendant(self, revision_id, other_branch,
3705
allow_diverged=False, allow_overwrite_descendant=False):
3706
# This performs additional work to meet the hook contract; while its
3707
# undesirable, we have to synthesise the revno to call the hook, and
3708
# not calling the hook is worse as it means changes can't be prevented.
3709
# Having calculated this though, we can't just call into
3710
# set_last_revision_info as a simple call, because there is a set_rh
3711
# hook that some folk may still be using.
3712
old_revno, old_revid = self.last_revision_info()
3713
history = self._lefthand_history(revision_id)
3714
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3715
err_context = {'other_branch': other_branch}
3716
response = self._call('Branch.set_last_revision_ex',
3717
self._remote_path(), self._lock_token, self._repo_lock_token,
3718
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3720
self._clear_cached_state()
3721
if len(response) != 3 and response[0] != 'ok':
3722
raise errors.UnexpectedSmartServerResponse(response)
3723
new_revno, new_revision_id = response[1:]
3724
self._last_revision_info_cache = new_revno, new_revision_id
3725
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3726
if self._real_branch is not None:
3727
cache = new_revno, new_revision_id
3728
self._real_branch._last_revision_info_cache = cache
3730
def _set_last_revision(self, revision_id):
3731
old_revno, old_revid = self.last_revision_info()
3732
# This performs additional work to meet the hook contract; while its
3733
# undesirable, we have to synthesise the revno to call the hook, and
3734
# not calling the hook is worse as it means changes can't be prevented.
3735
# Having calculated this though, we can't just call into
3736
# set_last_revision_info as a simple call, because there is a set_rh
3737
# hook that some folk may still be using.
3738
history = self._lefthand_history(revision_id)
3739
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3740
self._clear_cached_state()
3741
response = self._call('Branch.set_last_revision',
3742
self._remote_path(), self._lock_token, self._repo_lock_token,
3744
if response != ('ok',):
3745
raise errors.UnexpectedSmartServerResponse(response)
3746
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3748
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3750
def set_revision_history(self, rev_history):
3751
"""See Branch.set_revision_history."""
3752
self._set_revision_history(rev_history)
3755
def _set_revision_history(self, rev_history):
3756
# Send just the tip revision of the history; the server will generate
3757
# the full history from that. If the revision doesn't exist in this
3758
# branch, NoSuchRevision will be raised.
3759
if rev_history == []:
3762
rev_id = rev_history[-1]
3763
self._set_last_revision(rev_id)
3764
for hook in branch.Branch.hooks['set_rh']:
3765
hook(self, rev_history)
3766
self._cache_revision_history(rev_history)
3768
def _get_parent_location(self):
3769
medium = self._client._medium
3770
if medium._is_remote_before((1, 13)):
3771
return self._vfs_get_parent_location()
3773
response = self._call('Branch.get_parent', self._remote_path())
3774
except errors.UnknownSmartMethod:
3775
medium._remember_remote_is_before((1, 13))
3776
return self._vfs_get_parent_location()
3777
if len(response) != 1:
3778
raise errors.UnexpectedSmartServerResponse(response)
3779
parent_location = response[0]
3780
if parent_location == '':
3782
return parent_location
3784
def _vfs_get_parent_location(self):
3786
return self._real_branch._get_parent_location()
3788
def _set_parent_location(self, url):
3789
medium = self._client._medium
3790
if medium._is_remote_before((1, 15)):
3791
return self._vfs_set_parent_location(url)
3793
call_url = url or ''
3794
if type(call_url) is not str:
3795
raise AssertionError('url must be a str or None (%s)' % url)
3796
response = self._call('Branch.set_parent_location',
3797
self._remote_path(), self._lock_token, self._repo_lock_token,
3799
except errors.UnknownSmartMethod:
3800
medium._remember_remote_is_before((1, 15))
3801
return self._vfs_set_parent_location(url)
3803
raise errors.UnexpectedSmartServerResponse(response)
3805
def _vfs_set_parent_location(self, url):
3807
return self._real_branch._set_parent_location(url)
3810
def pull(self, source, overwrite=False, stop_revision=None,
3812
self._clear_cached_state_of_remote_branch_only()
3814
return self._real_branch.pull(
3815
source, overwrite=overwrite, stop_revision=stop_revision,
3816
_override_hook_target=self, **kwargs)
3819
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3821
return self._real_branch.push(
3822
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3823
_override_hook_source_branch=self)
3825
def is_locked(self):
3826
return self._lock_count >= 1
3829
def revision_id_to_dotted_revno(self, revision_id):
3830
"""Given a revision id, return its dotted revno.
3832
:return: a tuple like (1,) or (400,1,3).
3835
response = self._call('Branch.revision_id_to_revno',
3836
self._remote_path(), revision_id)
3837
except errors.UnknownSmartMethod:
3839
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3840
if response[0] == 'ok':
3841
return tuple([int(x) for x in response[1:]])
3843
raise errors.UnexpectedSmartServerResponse(response)
3846
def revision_id_to_revno(self, revision_id):
3847
"""Given a revision id on the branch mainline, return its revno.
3852
response = self._call('Branch.revision_id_to_revno',
3853
self._remote_path(), revision_id)
3854
except errors.UnknownSmartMethod:
3856
return self._real_branch.revision_id_to_revno(revision_id)
3857
if response[0] == 'ok':
3858
if len(response) == 2:
3859
return int(response[1])
3860
raise NoSuchRevision(self, revision_id)
3862
raise errors.UnexpectedSmartServerResponse(response)
3865
def set_last_revision_info(self, revno, revision_id):
3866
# XXX: These should be returned by the set_last_revision_info verb
3867
old_revno, old_revid = self.last_revision_info()
3868
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3869
if not revision_id or not isinstance(revision_id, basestring):
3870
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3872
response = self._call('Branch.set_last_revision_info',
3873
self._remote_path(), self._lock_token, self._repo_lock_token,
3874
str(revno), revision_id)
3875
except errors.UnknownSmartMethod:
3877
self._clear_cached_state_of_remote_branch_only()
3878
self._real_branch.set_last_revision_info(revno, revision_id)
3879
self._last_revision_info_cache = revno, revision_id
3881
if response == ('ok',):
3882
self._clear_cached_state()
3883
self._last_revision_info_cache = revno, revision_id
3884
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3885
# Update the _real_branch's cache too.
3886
if self._real_branch is not None:
3887
cache = self._last_revision_info_cache
3888
self._real_branch._last_revision_info_cache = cache
3890
raise errors.UnexpectedSmartServerResponse(response)
3893
def generate_revision_history(self, revision_id, last_rev=None,
3895
medium = self._client._medium
3896
if not medium._is_remote_before((1, 6)):
3897
# Use a smart method for 1.6 and above servers
3899
self._set_last_revision_descendant(revision_id, other_branch,
3900
allow_diverged=True, allow_overwrite_descendant=True)
3902
except errors.UnknownSmartMethod:
3903
medium._remember_remote_is_before((1, 6))
3904
self._clear_cached_state_of_remote_branch_only()
3905
self._set_revision_history(self._lefthand_history(revision_id,
3906
last_rev=last_rev,other_branch=other_branch))
3908
def set_push_location(self, location):
3910
return self._real_branch.set_push_location(location)
3912
def heads_to_fetch(self):
3913
if self._format._use_default_local_heads_to_fetch():
3914
# We recognise this format, and its heads-to-fetch implementation
3915
# is the default one (tip + tags). In this case it's cheaper to
3916
# just use the default implementation rather than a special RPC as
3917
# the tip and tags data is cached.
3918
return branch.Branch.heads_to_fetch(self)
3919
medium = self._client._medium
3920
if medium._is_remote_before((2, 4)):
3921
return self._vfs_heads_to_fetch()
3923
return self._rpc_heads_to_fetch()
3924
except errors.UnknownSmartMethod:
3925
medium._remember_remote_is_before((2, 4))
3926
return self._vfs_heads_to_fetch()
3928
def _rpc_heads_to_fetch(self):
3929
response = self._call('Branch.heads_to_fetch', self._remote_path())
3930
if len(response) != 2:
3931
raise errors.UnexpectedSmartServerResponse(response)
3932
must_fetch, if_present_fetch = response
3933
return set(must_fetch), set(if_present_fetch)
3935
def _vfs_heads_to_fetch(self):
3937
return self._real_branch.heads_to_fetch()
3940
class RemoteConfig(object):
3941
"""A Config that reads and writes from smart verbs.
3943
It is a low-level object that considers config data to be name/value pairs
3944
that may be associated with a section. Assigning meaning to the these
3945
values is done at higher levels like bzrlib.config.TreeConfig.
3948
def get_option(self, name, section=None, default=None):
3949
"""Return the value associated with a named option.
3951
:param name: The name of the value
3952
:param section: The section the option is in (if any)
3953
:param default: The value to return if the value is not set
3954
:return: The value or default value
3957
configobj = self._get_configobj()
3960
section_obj = configobj
3963
section_obj = configobj[section]
3966
if section_obj is None:
3969
value = section_obj.get(name, default)
3970
except errors.UnknownSmartMethod:
3971
value = self._vfs_get_option(name, section, default)
3972
for hook in _mod_config.OldConfigHooks['get']:
3973
hook(self, name, value)
3976
def _response_to_configobj(self, response):
3977
if len(response[0]) and response[0][0] != 'ok':
3978
raise errors.UnexpectedSmartServerResponse(response)
3979
lines = response[1].read_body_bytes().splitlines()
3980
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
3981
for hook in _mod_config.OldConfigHooks['load']:
3986
class RemoteBranchConfig(RemoteConfig):
3987
"""A RemoteConfig for Branches."""
3989
def __init__(self, branch):
3990
self._branch = branch
3992
def _get_configobj(self):
3993
path = self._branch._remote_path()
3994
response = self._branch._client.call_expecting_body(
3995
'Branch.get_config_file', path)
3996
return self._response_to_configobj(response)
3998
def set_option(self, value, name, section=None):
3999
"""Set the value associated with a named option.
4001
:param value: The value to set
4002
:param name: The name of the value to set
4003
:param section: The section the option is in (if any)
4005
medium = self._branch._client._medium
4006
if medium._is_remote_before((1, 14)):
4007
return self._vfs_set_option(value, name, section)
4008
if isinstance(value, dict):
4009
if medium._is_remote_before((2, 2)):
4010
return self._vfs_set_option(value, name, section)
4011
return self._set_config_option_dict(value, name, section)
4013
return self._set_config_option(value, name, section)
4015
def _set_config_option(self, value, name, section):
4017
path = self._branch._remote_path()
4018
response = self._branch._client.call('Branch.set_config_option',
4019
path, self._branch._lock_token, self._branch._repo_lock_token,
4020
value.encode('utf8'), name, section or '')
4021
except errors.UnknownSmartMethod:
4022
medium = self._branch._client._medium
4023
medium._remember_remote_is_before((1, 14))
4024
return self._vfs_set_option(value, name, section)
4026
raise errors.UnexpectedSmartServerResponse(response)
4028
def _serialize_option_dict(self, option_dict):
4030
for key, value in option_dict.items():
4031
if isinstance(key, unicode):
4032
key = key.encode('utf8')
4033
if isinstance(value, unicode):
4034
value = value.encode('utf8')
4035
utf8_dict[key] = value
4036
return bencode.bencode(utf8_dict)
4038
def _set_config_option_dict(self, value, name, section):
4040
path = self._branch._remote_path()
4041
serialised_dict = self._serialize_option_dict(value)
4042
response = self._branch._client.call(
4043
'Branch.set_config_option_dict',
4044
path, self._branch._lock_token, self._branch._repo_lock_token,
4045
serialised_dict, name, section or '')
4046
except errors.UnknownSmartMethod:
4047
medium = self._branch._client._medium
4048
medium._remember_remote_is_before((2, 2))
4049
return self._vfs_set_option(value, name, section)
4051
raise errors.UnexpectedSmartServerResponse(response)
4053
def _real_object(self):
4054
self._branch._ensure_real()
4055
return self._branch._real_branch
4057
def _vfs_set_option(self, value, name, section=None):
4058
return self._real_object()._get_config().set_option(
4059
value, name, section)
4062
class RemoteBzrDirConfig(RemoteConfig):
4063
"""A RemoteConfig for BzrDirs."""
4065
def __init__(self, bzrdir):
4066
self._bzrdir = bzrdir
4068
def _get_configobj(self):
4069
medium = self._bzrdir._client._medium
4070
verb = 'BzrDir.get_config_file'
4071
if medium._is_remote_before((1, 15)):
4072
raise errors.UnknownSmartMethod(verb)
4073
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4074
response = self._bzrdir._call_expecting_body(
4076
return self._response_to_configobj(response)
4078
def _vfs_get_option(self, name, section, default):
4079
return self._real_object()._get_config().get_option(
4080
name, section, default)
4082
def set_option(self, value, name, section=None):
4083
"""Set the value associated with a named option.
4085
:param value: The value to set
4086
:param name: The name of the value to set
4087
:param section: The section the option is in (if any)
4089
return self._real_object()._get_config().set_option(
4090
value, name, section)
4092
def _real_object(self):
4093
self._bzrdir._ensure_real()
4094
return self._bzrdir._real_bzrdir
4097
def _extract_tar(tar, to_dir):
4098
"""Extract all the contents of a tarfile object.
4100
A replacement for extractall, which is not present in python2.4
4103
tar.extract(tarinfo, to_dir)
4106
error_translators = registry.Registry()
4107
no_context_error_translators = registry.Registry()
4110
def _translate_error(err, **context):
4111
"""Translate an ErrorFromSmartServer into a more useful error.
4113
Possible context keys:
4121
If the error from the server doesn't match a known pattern, then
4122
UnknownErrorFromSmartServer is raised.
4126
return context[name]
4127
except KeyError, key_err:
4128
mutter('Missing key %r in context %r', key_err.args[0], context)
4131
"""Get the path from the context if present, otherwise use first error
4135
return context['path']
4136
except KeyError, key_err:
4138
return err.error_args[0]
4139
except IndexError, idx_err:
4141
'Missing key %r in context %r', key_err.args[0], context)
4145
translator = error_translators.get(err.error_verb)
4149
raise translator(err, find, get_path)
4151
translator = no_context_error_translators.get(err.error_verb)
4153
raise errors.UnknownErrorFromSmartServer(err)
4155
raise translator(err)
4158
error_translators.register('NoSuchRevision',
4159
lambda err, find, get_path: NoSuchRevision(
4160
find('branch'), err.error_args[0]))
4161
error_translators.register('nosuchrevision',
4162
lambda err, find, get_path: NoSuchRevision(
4163
find('repository'), err.error_args[0]))
4165
def _translate_nobranch_error(err, find, get_path):
4166
if len(err.error_args) >= 1:
4167
extra = err.error_args[0]
4170
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4173
error_translators.register('nobranch', _translate_nobranch_error)
4174
error_translators.register('norepository',
4175
lambda err, find, get_path: errors.NoRepositoryPresent(
4177
error_translators.register('UnlockableTransport',
4178
lambda err, find, get_path: errors.UnlockableTransport(
4179
find('bzrdir').root_transport))
4180
error_translators.register('TokenMismatch',
4181
lambda err, find, get_path: errors.TokenMismatch(
4182
find('token'), '(remote token)'))
4183
error_translators.register('Diverged',
4184
lambda err, find, get_path: errors.DivergedBranches(
4185
find('branch'), find('other_branch')))
4186
error_translators.register('NotStacked',
4187
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4189
def _translate_PermissionDenied(err, find, get_path):
4191
if len(err.error_args) >= 2:
4192
extra = err.error_args[1]
4195
return errors.PermissionDenied(path, extra=extra)
4197
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4198
error_translators.register('ReadError',
4199
lambda err, find, get_path: errors.ReadError(get_path()))
4200
error_translators.register('NoSuchFile',
4201
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4202
error_translators.register('TokenLockingNotSupported',
4203
lambda err, find, get_path: errors.TokenLockingNotSupported(
4204
find('repository')))
4205
error_translators.register('UnsuspendableWriteGroup',
4206
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4207
repository=find('repository')))
4208
error_translators.register('UnresumableWriteGroup',
4209
lambda err, find, get_path: errors.UnresumableWriteGroup(
4210
repository=find('repository'), write_groups=err.error_args[0],
4211
reason=err.error_args[1]))
4212
no_context_error_translators.register('IncompatibleRepositories',
4213
lambda err: errors.IncompatibleRepositories(
4214
err.error_args[0], err.error_args[1], err.error_args[2]))
4215
no_context_error_translators.register('LockContention',
4216
lambda err: errors.LockContention('(remote lock)'))
4217
no_context_error_translators.register('LockFailed',
4218
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4219
no_context_error_translators.register('TipChangeRejected',
4220
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4221
no_context_error_translators.register('UnstackableBranchFormat',
4222
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4223
no_context_error_translators.register('UnstackableRepositoryFormat',
4224
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4225
no_context_error_translators.register('FileExists',
4226
lambda err: errors.FileExists(err.error_args[0]))
4227
no_context_error_translators.register('DirectoryNotEmpty',
4228
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4230
def _translate_short_readv_error(err):
4231
args = err.error_args
4232
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4235
no_context_error_translators.register('ShortReadvError',
4236
_translate_short_readv_error)
4238
def _translate_unicode_error(err):
4239
encoding = str(err.error_args[0]) # encoding must always be a string
4240
val = err.error_args[1]
4241
start = int(err.error_args[2])
4242
end = int(err.error_args[3])
4243
reason = str(err.error_args[4]) # reason must always be a string
4244
if val.startswith('u:'):
4245
val = val[2:].decode('utf-8')
4246
elif val.startswith('s:'):
4247
val = val[2:].decode('base64')
4248
if err.error_verb == 'UnicodeDecodeError':
4249
raise UnicodeDecodeError(encoding, val, start, end, reason)
4250
elif err.error_verb == 'UnicodeEncodeError':
4251
raise UnicodeEncodeError(encoding, val, start, end, reason)
4253
no_context_error_translators.register('UnicodeEncodeError',
4254
_translate_unicode_error)
4255
no_context_error_translators.register('UnicodeDecodeError',
4256
_translate_unicode_error)
4257
no_context_error_translators.register('ReadOnlyError',
4258
lambda err: errors.TransportNotPossible('readonly transport'))
4259
no_context_error_translators.register('MemoryError',
4260
lambda err: errors.BzrError("remote server out of memory\n"
4261
"Retry non-remotely, or contact the server admin for details."))
4262
no_context_error_translators.register('RevisionNotPresent',
4263
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4265
no_context_error_translators.register('BzrCheckError',
4266
lambda err: errors.BzrCheckError(msg=err.error_args[0]))