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_branches(self, possible_transports=None, ignore_fallbacks=False):
683
path = self._path_for_remote_call(self._client)
685
response, handler = self._call_expecting_body(
686
'BzrDir.get_branches', path)
687
except errors.UnknownSmartMethod:
689
return self._real_bzrdir.get_branches()
690
if response[0] != "success":
691
raise errors.UnexpectedSmartServerResponse(response)
692
body = bencode.bdecode(handler.read_body_bytes())
694
for (name, value) in body.iteritems():
695
ret[name] = self._open_branch(name, value[0], value[1],
696
possible_transports=possible_transports,
697
ignore_fallbacks=ignore_fallbacks)
700
def set_branch_reference(self, target_branch, name=None):
701
"""See BzrDir.set_branch_reference()."""
703
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
705
def get_branch_reference(self, name=None):
706
"""See BzrDir.get_branch_reference()."""
708
# XXX JRV20100304: Support opening colocated branches
709
raise errors.NoColocatedBranchSupport(self)
710
response = self._get_branch_reference()
711
if response[0] == 'ref':
716
def _get_branch_reference(self):
717
path = self._path_for_remote_call(self._client)
718
medium = self._client._medium
720
('BzrDir.open_branchV3', (2, 1)),
721
('BzrDir.open_branchV2', (1, 13)),
722
('BzrDir.open_branch', None),
724
for verb, required_version in candidate_calls:
725
if required_version and medium._is_remote_before(required_version):
728
response = self._call(verb, path)
729
except errors.UnknownSmartMethod:
730
if required_version is None:
732
medium._remember_remote_is_before(required_version)
735
if verb == 'BzrDir.open_branch':
736
if response[0] != 'ok':
737
raise errors.UnexpectedSmartServerResponse(response)
738
if response[1] != '':
739
return ('ref', response[1])
741
return ('branch', '')
742
if response[0] not in ('ref', 'branch'):
743
raise errors.UnexpectedSmartServerResponse(response)
746
def _get_tree_branch(self, name=None):
747
"""See BzrDir._get_tree_branch()."""
748
return None, self.open_branch(name=name)
750
def _open_branch(self, name, kind, location_or_format,
751
ignore_fallbacks=False, possible_transports=None):
753
# a branch reference, use the existing BranchReference logic.
754
format = BranchReferenceFormat()
755
return format.open(self, name=name, _found=True,
756
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
757
possible_transports=possible_transports)
758
branch_format_name = location_or_format
759
if not branch_format_name:
760
branch_format_name = None
761
format = RemoteBranchFormat(network_name=branch_format_name)
762
return RemoteBranch(self, self.find_repository(), format=format,
763
setup_stacking=not ignore_fallbacks, name=name,
764
possible_transports=possible_transports)
766
def open_branch(self, name=None, unsupported=False,
767
ignore_fallbacks=False, possible_transports=None):
769
raise NotImplementedError('unsupported flag support not implemented yet.')
770
if self._next_open_branch_result is not None:
771
# See create_branch for details.
772
result = self._next_open_branch_result
773
self._next_open_branch_result = None
775
response = self._get_branch_reference()
777
name = self._get_selected_branch()
778
return self._open_branch(name, response[0], response[1],
779
possible_transports=possible_transports,
780
ignore_fallbacks=ignore_fallbacks)
782
def _open_repo_v1(self, path):
783
verb = 'BzrDir.find_repository'
784
response = self._call(verb, path)
785
if response[0] != 'ok':
786
raise errors.UnexpectedSmartServerResponse(response)
787
# servers that only support the v1 method don't support external
790
repo = self._real_bzrdir.open_repository()
791
response = response + ('no', repo._format.network_name())
792
return response, repo
794
def _open_repo_v2(self, path):
795
verb = 'BzrDir.find_repositoryV2'
796
response = self._call(verb, path)
797
if response[0] != 'ok':
798
raise errors.UnexpectedSmartServerResponse(response)
800
repo = self._real_bzrdir.open_repository()
801
response = response + (repo._format.network_name(),)
802
return response, repo
804
def _open_repo_v3(self, path):
805
verb = 'BzrDir.find_repositoryV3'
806
medium = self._client._medium
807
if medium._is_remote_before((1, 13)):
808
raise errors.UnknownSmartMethod(verb)
810
response = self._call(verb, path)
811
except errors.UnknownSmartMethod:
812
medium._remember_remote_is_before((1, 13))
814
if response[0] != 'ok':
815
raise errors.UnexpectedSmartServerResponse(response)
816
return response, None
818
def open_repository(self):
819
path = self._path_for_remote_call(self._client)
821
for probe in [self._open_repo_v3, self._open_repo_v2,
824
response, real_repo = probe(path)
826
except errors.UnknownSmartMethod:
829
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
830
if response[0] != 'ok':
831
raise errors.UnexpectedSmartServerResponse(response)
832
if len(response) != 6:
833
raise SmartProtocolError('incorrect response length %s' % (response,))
834
if response[1] == '':
835
# repo is at this dir.
836
format = response_tuple_to_repo_format(response[2:])
837
# Used to support creating a real format instance when needed.
838
format._creating_bzrdir = self
839
remote_repo = RemoteRepository(self, format)
840
format._creating_repo = remote_repo
841
if real_repo is not None:
842
remote_repo._set_real_repository(real_repo)
845
raise errors.NoRepositoryPresent(self)
847
def has_workingtree(self):
848
if self._has_working_tree is None:
849
path = self._path_for_remote_call(self._client)
851
response = self._call('BzrDir.has_workingtree', path)
852
except errors.UnknownSmartMethod:
854
self._has_working_tree = self._real_bzrdir.has_workingtree()
856
if response[0] not in ('yes', 'no'):
857
raise SmartProtocolError('unexpected response code %s' % (response,))
858
self._has_working_tree = (response[0] == 'yes')
859
return self._has_working_tree
861
def open_workingtree(self, recommend_upgrade=True):
862
if self.has_workingtree():
863
raise errors.NotLocalUrl(self.root_transport)
865
raise errors.NoWorkingTree(self.root_transport.base)
867
def _path_for_remote_call(self, client):
868
"""Return the path to be used for this bzrdir in a remote call."""
869
return urlutils.split_segment_parameters_raw(
870
client.remote_path_from_transport(self.root_transport))[0]
872
def get_branch_transport(self, branch_format, name=None):
874
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
876
def get_repository_transport(self, repository_format):
878
return self._real_bzrdir.get_repository_transport(repository_format)
880
def get_workingtree_transport(self, workingtree_format):
882
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
884
def can_convert_format(self):
885
"""Upgrading of remote bzrdirs is not supported yet."""
888
def needs_format_conversion(self, format):
889
"""Upgrading of remote bzrdirs is not supported yet."""
892
def _get_config(self):
893
return RemoteBzrDirConfig(self)
895
def _get_config_store(self):
896
return RemoteControlStore(self)
899
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
900
"""Format for repositories accessed over a _SmartClient.
902
Instances of this repository are represented by RemoteRepository
905
The RemoteRepositoryFormat is parameterized during construction
906
to reflect the capabilities of the real, remote format. Specifically
907
the attributes rich_root_data and supports_tree_reference are set
908
on a per instance basis, and are not set (and should not be) at
911
:ivar _custom_format: If set, a specific concrete repository format that
912
will be used when initializing a repository with this
913
RemoteRepositoryFormat.
914
:ivar _creating_repo: If set, the repository object that this
915
RemoteRepositoryFormat was created for: it can be called into
916
to obtain data like the network name.
919
_matchingbzrdir = RemoteBzrDirFormat()
920
supports_full_versioned_files = True
921
supports_leaving_lock = True
924
_mod_repository.RepositoryFormat.__init__(self)
925
self._custom_format = None
926
self._network_name = None
927
self._creating_bzrdir = None
928
self._revision_graph_can_have_wrong_parents = None
929
self._supports_chks = None
930
self._supports_external_lookups = None
931
self._supports_tree_reference = None
932
self._supports_funky_characters = None
933
self._supports_nesting_repositories = None
934
self._rich_root_data = None
937
return "%s(_network_name=%r)" % (self.__class__.__name__,
941
def fast_deltas(self):
943
return self._custom_format.fast_deltas
946
def rich_root_data(self):
947
if self._rich_root_data is None:
949
self._rich_root_data = self._custom_format.rich_root_data
950
return self._rich_root_data
953
def supports_chks(self):
954
if self._supports_chks is None:
956
self._supports_chks = self._custom_format.supports_chks
957
return self._supports_chks
960
def supports_external_lookups(self):
961
if self._supports_external_lookups is None:
963
self._supports_external_lookups = \
964
self._custom_format.supports_external_lookups
965
return self._supports_external_lookups
968
def supports_funky_characters(self):
969
if self._supports_funky_characters is None:
971
self._supports_funky_characters = \
972
self._custom_format.supports_funky_characters
973
return self._supports_funky_characters
976
def supports_nesting_repositories(self):
977
if self._supports_nesting_repositories is None:
979
self._supports_nesting_repositories = \
980
self._custom_format.supports_nesting_repositories
981
return self._supports_nesting_repositories
984
def supports_tree_reference(self):
985
if self._supports_tree_reference is None:
987
self._supports_tree_reference = \
988
self._custom_format.supports_tree_reference
989
return self._supports_tree_reference
992
def revision_graph_can_have_wrong_parents(self):
993
if self._revision_graph_can_have_wrong_parents is None:
995
self._revision_graph_can_have_wrong_parents = \
996
self._custom_format.revision_graph_can_have_wrong_parents
997
return self._revision_graph_can_have_wrong_parents
999
def _vfs_initialize(self, a_bzrdir, shared):
1000
"""Helper for common code in initialize."""
1001
if self._custom_format:
1002
# Custom format requested
1003
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1004
elif self._creating_bzrdir is not None:
1005
# Use the format that the repository we were created to back
1007
prior_repo = self._creating_bzrdir.open_repository()
1008
prior_repo._ensure_real()
1009
result = prior_repo._real_repository._format.initialize(
1010
a_bzrdir, shared=shared)
1012
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1013
# support remote initialization.
1014
# We delegate to a real object at this point (as RemoteBzrDir
1015
# delegate to the repository format which would lead to infinite
1016
# recursion if we just called a_bzrdir.create_repository.
1017
a_bzrdir._ensure_real()
1018
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1019
if not isinstance(result, RemoteRepository):
1020
return self.open(a_bzrdir)
1024
def initialize(self, a_bzrdir, shared=False):
1025
# Being asked to create on a non RemoteBzrDir:
1026
if not isinstance(a_bzrdir, RemoteBzrDir):
1027
return self._vfs_initialize(a_bzrdir, shared)
1028
medium = a_bzrdir._client._medium
1029
if medium._is_remote_before((1, 13)):
1030
return self._vfs_initialize(a_bzrdir, shared)
1031
# Creating on a remote bzr dir.
1032
# 1) get the network name to use.
1033
if self._custom_format:
1034
network_name = self._custom_format.network_name()
1035
elif self._network_name:
1036
network_name = self._network_name
1038
# Select the current bzrlib default and ask for that.
1039
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1040
reference_format = reference_bzrdir_format.repository_format
1041
network_name = reference_format.network_name()
1042
# 2) try direct creation via RPC
1043
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1044
verb = 'BzrDir.create_repository'
1048
shared_str = 'False'
1050
response = a_bzrdir._call(verb, path, network_name, shared_str)
1051
except errors.UnknownSmartMethod:
1052
# Fallback - use vfs methods
1053
medium._remember_remote_is_before((1, 13))
1054
return self._vfs_initialize(a_bzrdir, shared)
1056
# Turn the response into a RemoteRepository object.
1057
format = response_tuple_to_repo_format(response[1:])
1058
# Used to support creating a real format instance when needed.
1059
format._creating_bzrdir = a_bzrdir
1060
remote_repo = RemoteRepository(a_bzrdir, format)
1061
format._creating_repo = remote_repo
1064
def open(self, a_bzrdir):
1065
if not isinstance(a_bzrdir, RemoteBzrDir):
1066
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1067
return a_bzrdir.open_repository()
1069
def _ensure_real(self):
1070
if self._custom_format is None:
1072
self._custom_format = _mod_repository.network_format_registry.get(
1075
raise errors.UnknownFormatError(kind='repository',
1076
format=self._network_name)
1079
def _fetch_order(self):
1081
return self._custom_format._fetch_order
1084
def _fetch_uses_deltas(self):
1086
return self._custom_format._fetch_uses_deltas
1089
def _fetch_reconcile(self):
1091
return self._custom_format._fetch_reconcile
1093
def get_format_description(self):
1095
return 'Remote: ' + self._custom_format.get_format_description()
1097
def __eq__(self, other):
1098
return self.__class__ is other.__class__
1100
def network_name(self):
1101
if self._network_name:
1102
return self._network_name
1103
self._creating_repo._ensure_real()
1104
return self._creating_repo._real_repository._format.network_name()
1107
def pack_compresses(self):
1109
return self._custom_format.pack_compresses
1112
def _serializer(self):
1114
return self._custom_format._serializer
1117
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1118
lock._RelockDebugMixin):
1119
"""Repository accessed over rpc.
1121
For the moment most operations are performed using local transport-backed
1125
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1126
"""Create a RemoteRepository instance.
1128
:param remote_bzrdir: The bzrdir hosting this repository.
1129
:param format: The RemoteFormat object to use.
1130
:param real_repository: If not None, a local implementation of the
1131
repository logic for the repository, usually accessing the data
1133
:param _client: Private testing parameter - override the smart client
1134
to be used by the repository.
1137
self._real_repository = real_repository
1139
self._real_repository = None
1140
self.bzrdir = remote_bzrdir
1142
self._client = remote_bzrdir._client
1144
self._client = _client
1145
self._format = format
1146
self._lock_mode = None
1147
self._lock_token = None
1148
self._write_group_tokens = None
1149
self._lock_count = 0
1150
self._leave_lock = False
1151
# Cache of revision parents; misses are cached during read locks, and
1152
# write locks when no _real_repository has been set.
1153
self._unstacked_provider = graph.CachingParentsProvider(
1154
get_parent_map=self._get_parent_map_rpc)
1155
self._unstacked_provider.disable_cache()
1157
# These depend on the actual remote format, so force them off for
1158
# maximum compatibility. XXX: In future these should depend on the
1159
# remote repository instance, but this is irrelevant until we perform
1160
# reconcile via an RPC call.
1161
self._reconcile_does_inventory_gc = False
1162
self._reconcile_fixes_text_parents = False
1163
self._reconcile_backsup_inventory = False
1164
self.base = self.bzrdir.transport.base
1165
# Additional places to query for data.
1166
self._fallback_repositories = []
1169
def user_transport(self):
1170
return self.bzrdir.user_transport
1173
def control_transport(self):
1174
# XXX: Normally you shouldn't directly get at the remote repository
1175
# transport, but I'm not sure it's worth making this method
1176
# optional -- mbp 2010-04-21
1177
return self.bzrdir.get_repository_transport(None)
1180
return "%s(%s)" % (self.__class__.__name__, self.base)
1184
def abort_write_group(self, suppress_errors=False):
1185
"""Complete a write group on the decorated repository.
1187
Smart methods perform operations in a single step so this API
1188
is not really applicable except as a compatibility thunk
1189
for older plugins that don't use e.g. the CommitBuilder
1192
:param suppress_errors: see Repository.abort_write_group.
1194
if self._real_repository:
1196
return self._real_repository.abort_write_group(
1197
suppress_errors=suppress_errors)
1198
if not self.is_in_write_group():
1200
mutter('(suppressed) not in write group')
1202
raise errors.BzrError("not in write group")
1203
path = self.bzrdir._path_for_remote_call(self._client)
1205
response = self._call('Repository.abort_write_group', path,
1206
self._lock_token, self._write_group_tokens)
1207
except Exception, exc:
1208
self._write_group = None
1209
if not suppress_errors:
1211
mutter('abort_write_group failed')
1212
log_exception_quietly()
1213
note(gettext('bzr: ERROR (ignored): %s'), exc)
1215
if response != ('ok', ):
1216
raise errors.UnexpectedSmartServerResponse(response)
1217
self._write_group_tokens = None
1220
def chk_bytes(self):
1221
"""Decorate the real repository for now.
1223
In the long term a full blown network facility is needed to avoid
1224
creating a real repository object locally.
1227
return self._real_repository.chk_bytes
1229
def commit_write_group(self):
1230
"""Complete a write group on the decorated repository.
1232
Smart methods perform operations in a single step so this API
1233
is not really applicable except as a compatibility thunk
1234
for older plugins that don't use e.g. the CommitBuilder
1237
if self._real_repository:
1239
return self._real_repository.commit_write_group()
1240
if not self.is_in_write_group():
1241
raise errors.BzrError("not in write group")
1242
path = self.bzrdir._path_for_remote_call(self._client)
1243
response = self._call('Repository.commit_write_group', path,
1244
self._lock_token, self._write_group_tokens)
1245
if response != ('ok', ):
1246
raise errors.UnexpectedSmartServerResponse(response)
1247
self._write_group_tokens = None
1248
# Refresh data after writing to the repository.
1251
def resume_write_group(self, tokens):
1252
if self._real_repository:
1253
return self._real_repository.resume_write_group(tokens)
1254
path = self.bzrdir._path_for_remote_call(self._client)
1256
response = self._call('Repository.check_write_group', path,
1257
self._lock_token, tokens)
1258
except errors.UnknownSmartMethod:
1260
return self._real_repository.resume_write_group(tokens)
1261
if response != ('ok', ):
1262
raise errors.UnexpectedSmartServerResponse(response)
1263
self._write_group_tokens = tokens
1265
def suspend_write_group(self):
1266
if self._real_repository:
1267
return self._real_repository.suspend_write_group()
1268
ret = self._write_group_tokens or []
1269
self._write_group_tokens = None
1272
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1274
return self._real_repository.get_missing_parent_inventories(
1275
check_for_missing_texts=check_for_missing_texts)
1277
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1279
return self._real_repository.get_rev_id_for_revno(
1282
def get_rev_id_for_revno(self, revno, known_pair):
1283
"""See Repository.get_rev_id_for_revno."""
1284
path = self.bzrdir._path_for_remote_call(self._client)
1286
if self._client._medium._is_remote_before((1, 17)):
1287
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1288
response = self._call(
1289
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1290
except errors.UnknownSmartMethod:
1291
self._client._medium._remember_remote_is_before((1, 17))
1292
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1293
if response[0] == 'ok':
1294
return True, response[1]
1295
elif response[0] == 'history-incomplete':
1296
known_pair = response[1:3]
1297
for fallback in self._fallback_repositories:
1298
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1303
# Not found in any fallbacks
1304
return False, known_pair
1306
raise errors.UnexpectedSmartServerResponse(response)
1308
def _ensure_real(self):
1309
"""Ensure that there is a _real_repository set.
1311
Used before calls to self._real_repository.
1313
Note that _ensure_real causes many roundtrips to the server which are
1314
not desirable, and prevents the use of smart one-roundtrip RPC's to
1315
perform complex operations (such as accessing parent data, streaming
1316
revisions etc). Adding calls to _ensure_real should only be done when
1317
bringing up new functionality, adding fallbacks for smart methods that
1318
require a fallback path, and never to replace an existing smart method
1319
invocation. If in doubt chat to the bzr network team.
1321
if self._real_repository is None:
1322
if 'hpssvfs' in debug.debug_flags:
1324
warning('VFS Repository access triggered\n%s',
1325
''.join(traceback.format_stack()))
1326
self._unstacked_provider.missing_keys.clear()
1327
self.bzrdir._ensure_real()
1328
self._set_real_repository(
1329
self.bzrdir._real_bzrdir.open_repository())
1331
def _translate_error(self, err, **context):
1332
self.bzrdir._translate_error(err, repository=self, **context)
1334
def find_text_key_references(self):
1335
"""Find the text key references within the repository.
1337
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1338
to whether they were referred to by the inventory of the
1339
revision_id that they contain. The inventory texts from all present
1340
revision ids are assessed to generate this report.
1343
return self._real_repository.find_text_key_references()
1345
def _generate_text_key_index(self):
1346
"""Generate a new text key index for the repository.
1348
This is an expensive function that will take considerable time to run.
1350
:return: A dict mapping (file_id, revision_id) tuples to a list of
1351
parents, also (file_id, revision_id) tuples.
1354
return self._real_repository._generate_text_key_index()
1356
def _get_revision_graph(self, revision_id):
1357
"""Private method for using with old (< 1.2) servers to fallback."""
1358
if revision_id is None:
1360
elif _mod_revision.is_null(revision_id):
1363
path = self.bzrdir._path_for_remote_call(self._client)
1364
response = self._call_expecting_body(
1365
'Repository.get_revision_graph', path, revision_id)
1366
response_tuple, response_handler = response
1367
if response_tuple[0] != 'ok':
1368
raise errors.UnexpectedSmartServerResponse(response_tuple)
1369
coded = response_handler.read_body_bytes()
1371
# no revisions in this repository!
1373
lines = coded.split('\n')
1376
d = tuple(line.split())
1377
revision_graph[d[0]] = d[1:]
1379
return revision_graph
1381
def _get_sink(self):
1382
"""See Repository._get_sink()."""
1383
return RemoteStreamSink(self)
1385
def _get_source(self, to_format):
1386
"""Return a source for streaming from this repository."""
1387
return RemoteStreamSource(self, to_format)
1390
def get_file_graph(self):
1391
return graph.Graph(self.texts)
1394
def has_revision(self, revision_id):
1395
"""True if this repository has a copy of the revision."""
1396
# Copy of bzrlib.repository.Repository.has_revision
1397
return revision_id in self.has_revisions((revision_id,))
1400
def has_revisions(self, revision_ids):
1401
"""Probe to find out the presence of multiple revisions.
1403
:param revision_ids: An iterable of revision_ids.
1404
:return: A set of the revision_ids that were present.
1406
# Copy of bzrlib.repository.Repository.has_revisions
1407
parent_map = self.get_parent_map(revision_ids)
1408
result = set(parent_map)
1409
if _mod_revision.NULL_REVISION in revision_ids:
1410
result.add(_mod_revision.NULL_REVISION)
1413
def _has_same_fallbacks(self, other_repo):
1414
"""Returns true if the repositories have the same fallbacks."""
1415
# XXX: copied from Repository; it should be unified into a base class
1416
# <https://bugs.launchpad.net/bzr/+bug/401622>
1417
my_fb = self._fallback_repositories
1418
other_fb = other_repo._fallback_repositories
1419
if len(my_fb) != len(other_fb):
1421
for f, g in zip(my_fb, other_fb):
1422
if not f.has_same_location(g):
1426
def has_same_location(self, other):
1427
# TODO: Move to RepositoryBase and unify with the regular Repository
1428
# one; unfortunately the tests rely on slightly different behaviour at
1429
# present -- mbp 20090710
1430
return (self.__class__ is other.__class__ and
1431
self.bzrdir.transport.base == other.bzrdir.transport.base)
1433
def get_graph(self, other_repository=None):
1434
"""Return the graph for this repository format"""
1435
parents_provider = self._make_parents_provider(other_repository)
1436
return graph.Graph(parents_provider)
1439
def get_known_graph_ancestry(self, revision_ids):
1440
"""Return the known graph for a set of revision ids and their ancestors.
1442
st = static_tuple.StaticTuple
1443
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1444
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1445
return graph.GraphThunkIdsToKeys(known_graph)
1447
def gather_stats(self, revid=None, committers=None):
1448
"""See Repository.gather_stats()."""
1449
path = self.bzrdir._path_for_remote_call(self._client)
1450
# revid can be None to indicate no revisions, not just NULL_REVISION
1451
if revid is None or _mod_revision.is_null(revid):
1455
if committers is None or not committers:
1456
fmt_committers = 'no'
1458
fmt_committers = 'yes'
1459
response_tuple, response_handler = self._call_expecting_body(
1460
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1461
if response_tuple[0] != 'ok':
1462
raise errors.UnexpectedSmartServerResponse(response_tuple)
1464
body = response_handler.read_body_bytes()
1466
for line in body.split('\n'):
1469
key, val_text = line.split(':')
1470
if key in ('revisions', 'size', 'committers'):
1471
result[key] = int(val_text)
1472
elif key in ('firstrev', 'latestrev'):
1473
values = val_text.split(' ')[1:]
1474
result[key] = (float(values[0]), long(values[1]))
1478
def find_branches(self, using=False):
1479
"""See Repository.find_branches()."""
1480
# should be an API call to the server.
1482
return self._real_repository.find_branches(using=using)
1484
def get_physical_lock_status(self):
1485
"""See Repository.get_physical_lock_status()."""
1486
path = self.bzrdir._path_for_remote_call(self._client)
1488
response = self._call('Repository.get_physical_lock_status', path)
1489
except errors.UnknownSmartMethod:
1491
return self._real_repository.get_physical_lock_status()
1492
if response[0] not in ('yes', 'no'):
1493
raise errors.UnexpectedSmartServerResponse(response)
1494
return (response[0] == 'yes')
1496
def is_in_write_group(self):
1497
"""Return True if there is an open write group.
1499
write groups are only applicable locally for the smart server..
1501
if self._write_group_tokens is not None:
1503
if self._real_repository:
1504
return self._real_repository.is_in_write_group()
1506
def is_locked(self):
1507
return self._lock_count >= 1
1509
def is_shared(self):
1510
"""See Repository.is_shared()."""
1511
path = self.bzrdir._path_for_remote_call(self._client)
1512
response = self._call('Repository.is_shared', path)
1513
if response[0] not in ('yes', 'no'):
1514
raise SmartProtocolError('unexpected response code %s' % (response,))
1515
return response[0] == 'yes'
1517
def is_write_locked(self):
1518
return self._lock_mode == 'w'
1520
def _warn_if_deprecated(self, branch=None):
1521
# If we have a real repository, the check will be done there, if we
1522
# don't the check will be done remotely.
1525
def lock_read(self):
1526
"""Lock the repository for read operations.
1528
:return: A bzrlib.lock.LogicalLockResult.
1530
# wrong eventually - want a local lock cache context
1531
if not self._lock_mode:
1532
self._note_lock('r')
1533
self._lock_mode = 'r'
1534
self._lock_count = 1
1535
self._unstacked_provider.enable_cache(cache_misses=True)
1536
if self._real_repository is not None:
1537
self._real_repository.lock_read()
1538
for repo in self._fallback_repositories:
1541
self._lock_count += 1
1542
return lock.LogicalLockResult(self.unlock)
1544
def _remote_lock_write(self, token):
1545
path = self.bzrdir._path_for_remote_call(self._client)
1548
err_context = {'token': token}
1549
response = self._call('Repository.lock_write', path, token,
1551
if response[0] == 'ok':
1552
ok, token = response
1555
raise errors.UnexpectedSmartServerResponse(response)
1557
def lock_write(self, token=None, _skip_rpc=False):
1558
if not self._lock_mode:
1559
self._note_lock('w')
1561
if self._lock_token is not None:
1562
if token != self._lock_token:
1563
raise errors.TokenMismatch(token, self._lock_token)
1564
self._lock_token = token
1566
self._lock_token = self._remote_lock_write(token)
1567
# if self._lock_token is None, then this is something like packs or
1568
# svn where we don't get to lock the repo, or a weave style repository
1569
# where we cannot lock it over the wire and attempts to do so will
1571
if self._real_repository is not None:
1572
self._real_repository.lock_write(token=self._lock_token)
1573
if token is not None:
1574
self._leave_lock = True
1576
self._leave_lock = False
1577
self._lock_mode = 'w'
1578
self._lock_count = 1
1579
cache_misses = self._real_repository is None
1580
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1581
for repo in self._fallback_repositories:
1582
# Writes don't affect fallback repos
1584
elif self._lock_mode == 'r':
1585
raise errors.ReadOnlyError(self)
1587
self._lock_count += 1
1588
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1590
def leave_lock_in_place(self):
1591
if not self._lock_token:
1592
raise NotImplementedError(self.leave_lock_in_place)
1593
self._leave_lock = True
1595
def dont_leave_lock_in_place(self):
1596
if not self._lock_token:
1597
raise NotImplementedError(self.dont_leave_lock_in_place)
1598
self._leave_lock = False
1600
def _set_real_repository(self, repository):
1601
"""Set the _real_repository for this repository.
1603
:param repository: The repository to fallback to for non-hpss
1604
implemented operations.
1606
if self._real_repository is not None:
1607
# Replacing an already set real repository.
1608
# We cannot do this [currently] if the repository is locked -
1609
# synchronised state might be lost.
1610
if self.is_locked():
1611
raise AssertionError('_real_repository is already set')
1612
if isinstance(repository, RemoteRepository):
1613
raise AssertionError()
1614
self._real_repository = repository
1615
# three code paths happen here:
1616
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1617
# up stacking. In this case self._fallback_repositories is [], and the
1618
# real repo is already setup. Preserve the real repo and
1619
# RemoteRepository.add_fallback_repository will avoid adding
1621
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1622
# ensure_real is triggered from a branch, the real repository to
1623
# set already has a matching list with separate instances, but
1624
# as they are also RemoteRepositories we don't worry about making the
1625
# lists be identical.
1626
# 3) new servers, RemoteRepository.ensure_real is triggered before
1627
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1628
# and need to populate it.
1629
if (self._fallback_repositories and
1630
len(self._real_repository._fallback_repositories) !=
1631
len(self._fallback_repositories)):
1632
if len(self._real_repository._fallback_repositories):
1633
raise AssertionError(
1634
"cannot cleanly remove existing _fallback_repositories")
1635
for fb in self._fallback_repositories:
1636
self._real_repository.add_fallback_repository(fb)
1637
if self._lock_mode == 'w':
1638
# if we are already locked, the real repository must be able to
1639
# acquire the lock with our token.
1640
self._real_repository.lock_write(self._lock_token)
1641
elif self._lock_mode == 'r':
1642
self._real_repository.lock_read()
1643
if self._write_group_tokens is not None:
1644
# if we are already in a write group, resume it
1645
self._real_repository.resume_write_group(self._write_group_tokens)
1646
self._write_group_tokens = None
1648
def start_write_group(self):
1649
"""Start a write group on the decorated repository.
1651
Smart methods perform operations in a single step so this API
1652
is not really applicable except as a compatibility thunk
1653
for older plugins that don't use e.g. the CommitBuilder
1656
if self._real_repository:
1658
return self._real_repository.start_write_group()
1659
if not self.is_write_locked():
1660
raise errors.NotWriteLocked(self)
1661
if self._write_group_tokens is not None:
1662
raise errors.BzrError('already in a write group')
1663
path = self.bzrdir._path_for_remote_call(self._client)
1665
response = self._call('Repository.start_write_group', path,
1667
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1669
return self._real_repository.start_write_group()
1670
if response[0] != 'ok':
1671
raise errors.UnexpectedSmartServerResponse(response)
1672
self._write_group_tokens = response[1]
1674
def _unlock(self, token):
1675
path = self.bzrdir._path_for_remote_call(self._client)
1677
# with no token the remote repository is not persistently locked.
1679
err_context = {'token': token}
1680
response = self._call('Repository.unlock', path, token,
1682
if response == ('ok',):
1685
raise errors.UnexpectedSmartServerResponse(response)
1687
@only_raises(errors.LockNotHeld, errors.LockBroken)
1689
if not self._lock_count:
1690
return lock.cant_unlock_not_held(self)
1691
self._lock_count -= 1
1692
if self._lock_count > 0:
1694
self._unstacked_provider.disable_cache()
1695
old_mode = self._lock_mode
1696
self._lock_mode = None
1698
# The real repository is responsible at present for raising an
1699
# exception if it's in an unfinished write group. However, it
1700
# normally will *not* actually remove the lock from disk - that's
1701
# done by the server on receiving the Repository.unlock call.
1702
# This is just to let the _real_repository stay up to date.
1703
if self._real_repository is not None:
1704
self._real_repository.unlock()
1705
elif self._write_group_tokens is not None:
1706
self.abort_write_group()
1708
# The rpc-level lock should be released even if there was a
1709
# problem releasing the vfs-based lock.
1711
# Only write-locked repositories need to make a remote method
1712
# call to perform the unlock.
1713
old_token = self._lock_token
1714
self._lock_token = None
1715
if not self._leave_lock:
1716
self._unlock(old_token)
1717
# Fallbacks are always 'lock_read()' so we don't pay attention to
1719
for repo in self._fallback_repositories:
1722
def break_lock(self):
1723
# should hand off to the network
1724
path = self.bzrdir._path_for_remote_call(self._client)
1726
response = self._call("Repository.break_lock", path)
1727
except errors.UnknownSmartMethod:
1729
return self._real_repository.break_lock()
1730
if response != ('ok',):
1731
raise errors.UnexpectedSmartServerResponse(response)
1733
def _get_tarball(self, compression):
1734
"""Return a TemporaryFile containing a repository tarball.
1736
Returns None if the server does not support sending tarballs.
1739
path = self.bzrdir._path_for_remote_call(self._client)
1741
response, protocol = self._call_expecting_body(
1742
'Repository.tarball', path, compression)
1743
except errors.UnknownSmartMethod:
1744
protocol.cancel_read_body()
1746
if response[0] == 'ok':
1747
# Extract the tarball and return it
1748
t = tempfile.NamedTemporaryFile()
1749
# TODO: rpc layer should read directly into it...
1750
t.write(protocol.read_body_bytes())
1753
raise errors.UnexpectedSmartServerResponse(response)
1756
def sprout(self, to_bzrdir, revision_id=None):
1757
"""Create a descendent repository for new development.
1759
Unlike clone, this does not copy the settings of the repository.
1761
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1762
dest_repo.fetch(self, revision_id=revision_id)
1765
def _create_sprouting_repo(self, a_bzrdir, shared):
1766
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1767
# use target default format.
1768
dest_repo = a_bzrdir.create_repository()
1770
# Most control formats need the repository to be specifically
1771
# created, but on some old all-in-one formats it's not needed
1773
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1774
except errors.UninitializableFormat:
1775
dest_repo = a_bzrdir.open_repository()
1778
### These methods are just thin shims to the VFS object for now.
1781
def revision_tree(self, revision_id):
1782
revision_id = _mod_revision.ensure_null(revision_id)
1783
if revision_id == _mod_revision.NULL_REVISION:
1784
return InventoryRevisionTree(self,
1785
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1787
return list(self.revision_trees([revision_id]))[0]
1789
def get_serializer_format(self):
1790
path = self.bzrdir._path_for_remote_call(self._client)
1792
response = self._call('VersionedFileRepository.get_serializer_format',
1794
except errors.UnknownSmartMethod:
1796
return self._real_repository.get_serializer_format()
1797
if response[0] != 'ok':
1798
raise errors.UnexpectedSmartServerResponse(response)
1801
def get_commit_builder(self, branch, parents, config, timestamp=None,
1802
timezone=None, committer=None, revprops=None,
1803
revision_id=None, lossy=False):
1804
"""Obtain a CommitBuilder for this repository.
1806
:param branch: Branch to commit to.
1807
:param parents: Revision ids of the parents of the new revision.
1808
:param config: Configuration to use.
1809
:param timestamp: Optional timestamp recorded for commit.
1810
:param timezone: Optional timezone for timestamp.
1811
:param committer: Optional committer to set for commit.
1812
:param revprops: Optional dictionary of revision properties.
1813
:param revision_id: Optional revision id.
1814
:param lossy: Whether to discard data that can not be natively
1815
represented, when pushing to a foreign VCS
1817
if self._fallback_repositories and not self._format.supports_chks:
1818
raise errors.BzrError("Cannot commit directly to a stacked branch"
1819
" in pre-2a formats. See "
1820
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1821
if self._format.rich_root_data:
1822
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1824
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1825
result = commit_builder_kls(self, parents, config,
1826
timestamp, timezone, committer, revprops, revision_id,
1828
self.start_write_group()
1831
def add_fallback_repository(self, repository):
1832
"""Add a repository to use for looking up data not held locally.
1834
:param repository: A repository.
1836
if not self._format.supports_external_lookups:
1837
raise errors.UnstackableRepositoryFormat(
1838
self._format.network_name(), self.base)
1839
# We need to accumulate additional repositories here, to pass them in
1842
# Make the check before we lock: this raises an exception.
1843
self._check_fallback_repository(repository)
1844
if self.is_locked():
1845
# We will call fallback.unlock() when we transition to the unlocked
1846
# state, so always add a lock here. If a caller passes us a locked
1847
# repository, they are responsible for unlocking it later.
1848
repository.lock_read()
1849
self._fallback_repositories.append(repository)
1850
# If self._real_repository was parameterised already (e.g. because a
1851
# _real_branch had its get_stacked_on_url method called), then the
1852
# repository to be added may already be in the _real_repositories list.
1853
if self._real_repository is not None:
1854
fallback_locations = [repo.user_url for repo in
1855
self._real_repository._fallback_repositories]
1856
if repository.user_url not in fallback_locations:
1857
self._real_repository.add_fallback_repository(repository)
1859
def _check_fallback_repository(self, repository):
1860
"""Check that this repository can fallback to repository safely.
1862
Raise an error if not.
1864
:param repository: A repository to fallback to.
1866
return _mod_repository.InterRepository._assert_same_model(
1869
def add_inventory(self, revid, inv, parents):
1871
return self._real_repository.add_inventory(revid, inv, parents)
1873
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1874
parents, basis_inv=None, propagate_caches=False):
1876
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1877
delta, new_revision_id, parents, basis_inv=basis_inv,
1878
propagate_caches=propagate_caches)
1880
def add_revision(self, revision_id, rev, inv=None):
1881
_mod_revision.check_not_reserved_id(revision_id)
1882
key = (revision_id,)
1883
# check inventory present
1884
if not self.inventories.get_parent_map([key]):
1886
raise errors.WeaveRevisionNotPresent(revision_id,
1889
# yes, this is not suitable for adding with ghosts.
1890
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1893
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1894
self._add_revision(rev)
1896
def _add_revision(self, rev):
1897
if self._real_repository is not None:
1898
return self._real_repository._add_revision(rev)
1899
text = self._serializer.write_revision_to_string(rev)
1900
key = (rev.revision_id,)
1901
parents = tuple((parent,) for parent in rev.parent_ids)
1902
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1903
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1904
self._format, self._write_group_tokens)
1907
def get_inventory(self, revision_id):
1908
return list(self.iter_inventories([revision_id]))[0]
1910
def _iter_inventories_rpc(self, revision_ids, ordering):
1911
if ordering is None:
1912
ordering = 'unordered'
1913
path = self.bzrdir._path_for_remote_call(self._client)
1914
body = "\n".join(revision_ids)
1915
response_tuple, response_handler = (
1916
self._call_with_body_bytes_expecting_body(
1917
"VersionedFileRepository.get_inventories",
1918
(path, ordering), body))
1919
if response_tuple[0] != "ok":
1920
raise errors.UnexpectedSmartServerResponse(response_tuple)
1921
deserializer = inventory_delta.InventoryDeltaDeserializer()
1922
byte_stream = response_handler.read_streamed_body()
1923
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1925
# no results whatsoever
1927
src_format, stream = decoded
1928
if src_format.network_name() != self._format.network_name():
1929
raise AssertionError(
1930
"Mismatched RemoteRepository and stream src %r, %r" % (
1931
src_format.network_name(), self._format.network_name()))
1932
# ignore the src format, it's not really relevant
1933
prev_inv = Inventory(root_id=None,
1934
revision_id=_mod_revision.NULL_REVISION)
1935
# there should be just one substream, with inventory deltas
1936
substream_kind, substream = stream.next()
1937
if substream_kind != "inventory-deltas":
1938
raise AssertionError(
1939
"Unexpected stream %r received" % substream_kind)
1940
for record in substream:
1941
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1942
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1943
if parent_id != prev_inv.revision_id:
1944
raise AssertionError("invalid base %r != %r" % (parent_id,
1945
prev_inv.revision_id))
1946
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1947
yield inv, inv.revision_id
1950
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1952
return self._real_repository._iter_inventories(revision_ids, ordering)
1954
def iter_inventories(self, revision_ids, ordering=None):
1955
"""Get many inventories by revision_ids.
1957
This will buffer some or all of the texts used in constructing the
1958
inventories in memory, but will only parse a single inventory at a
1961
:param revision_ids: The expected revision ids of the inventories.
1962
:param ordering: optional ordering, e.g. 'topological'. If not
1963
specified, the order of revision_ids will be preserved (by
1964
buffering if necessary).
1965
:return: An iterator of inventories.
1967
if ((None in revision_ids)
1968
or (_mod_revision.NULL_REVISION in revision_ids)):
1969
raise ValueError('cannot get null revision inventory')
1970
for inv, revid in self._iter_inventories(revision_ids, ordering):
1972
raise errors.NoSuchRevision(self, revid)
1975
def _iter_inventories(self, revision_ids, ordering=None):
1976
if len(revision_ids) == 0:
1978
missing = set(revision_ids)
1979
if ordering is None:
1980
order_as_requested = True
1982
order = list(revision_ids)
1984
next_revid = order.pop()
1986
order_as_requested = False
1987
if ordering != 'unordered' and self._fallback_repositories:
1988
raise ValueError('unsupported ordering %r' % ordering)
1989
iter_inv_fns = [self._iter_inventories_rpc] + [
1990
fallback._iter_inventories for fallback in
1991
self._fallback_repositories]
1993
for iter_inv in iter_inv_fns:
1994
request = [revid for revid in revision_ids if revid in missing]
1995
for inv, revid in iter_inv(request, ordering):
1998
missing.remove(inv.revision_id)
1999
if ordering != 'unordered':
2003
if order_as_requested:
2004
# Yield as many results as we can while preserving order.
2005
while next_revid in invs:
2006
inv = invs.pop(next_revid)
2007
yield inv, inv.revision_id
2009
next_revid = order.pop()
2011
# We still want to fully consume the stream, just
2012
# in case it is not actually finished at this point
2015
except errors.UnknownSmartMethod:
2016
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2020
if order_as_requested:
2021
if next_revid is not None:
2022
yield None, next_revid
2025
yield invs.get(revid), revid
2028
yield None, missing.pop()
2031
def get_revision(self, revision_id):
2032
return self.get_revisions([revision_id])[0]
2034
def get_transaction(self):
2036
return self._real_repository.get_transaction()
2039
def clone(self, a_bzrdir, revision_id=None):
2040
dest_repo = self._create_sprouting_repo(
2041
a_bzrdir, shared=self.is_shared())
2042
self.copy_content_into(dest_repo, revision_id)
2045
def make_working_trees(self):
2046
"""See Repository.make_working_trees"""
2047
path = self.bzrdir._path_for_remote_call(self._client)
2049
response = self._call('Repository.make_working_trees', path)
2050
except errors.UnknownSmartMethod:
2052
return self._real_repository.make_working_trees()
2053
if response[0] not in ('yes', 'no'):
2054
raise SmartProtocolError('unexpected response code %s' % (response,))
2055
return response[0] == 'yes'
2057
def refresh_data(self):
2058
"""Re-read any data needed to synchronise with disk.
2060
This method is intended to be called after another repository instance
2061
(such as one used by a smart server) has inserted data into the
2062
repository. On all repositories this will work outside of write groups.
2063
Some repository formats (pack and newer for bzrlib native formats)
2064
support refresh_data inside write groups. If called inside a write
2065
group on a repository that does not support refreshing in a write group
2066
IsInWriteGroupError will be raised.
2068
if self._real_repository is not None:
2069
self._real_repository.refresh_data()
2070
# Refresh the parents cache for this object
2071
self._unstacked_provider.disable_cache()
2072
self._unstacked_provider.enable_cache()
2074
def revision_ids_to_search_result(self, result_set):
2075
"""Convert a set of revision ids to a graph SearchResult."""
2076
result_parents = set()
2077
for parents in self.get_graph().get_parent_map(
2078
result_set).itervalues():
2079
result_parents.update(parents)
2080
included_keys = result_set.intersection(result_parents)
2081
start_keys = result_set.difference(included_keys)
2082
exclude_keys = result_parents.difference(result_set)
2083
result = vf_search.SearchResult(start_keys, exclude_keys,
2084
len(result_set), result_set)
2088
def search_missing_revision_ids(self, other,
2089
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2090
find_ghosts=True, revision_ids=None, if_present_ids=None,
2092
"""Return the revision ids that other has that this does not.
2094
These are returned in topological order.
2096
revision_id: only return revision ids included by revision_id.
2098
if symbol_versioning.deprecated_passed(revision_id):
2099
symbol_versioning.warn(
2100
'search_missing_revision_ids(revision_id=...) was '
2101
'deprecated in 2.4. Use revision_ids=[...] instead.',
2102
DeprecationWarning, stacklevel=2)
2103
if revision_ids is not None:
2104
raise AssertionError(
2105
'revision_ids is mutually exclusive with revision_id')
2106
if revision_id is not None:
2107
revision_ids = [revision_id]
2108
inter_repo = _mod_repository.InterRepository.get(other, self)
2109
return inter_repo.search_missing_revision_ids(
2110
find_ghosts=find_ghosts, revision_ids=revision_ids,
2111
if_present_ids=if_present_ids, limit=limit)
2113
def fetch(self, source, revision_id=None, find_ghosts=False,
2115
# No base implementation to use as RemoteRepository is not a subclass
2116
# of Repository; so this is a copy of Repository.fetch().
2117
if fetch_spec is not None and revision_id is not None:
2118
raise AssertionError(
2119
"fetch_spec and revision_id are mutually exclusive.")
2120
if self.is_in_write_group():
2121
raise errors.InternalBzrError(
2122
"May not fetch while in a write group.")
2123
# fast path same-url fetch operations
2124
if (self.has_same_location(source)
2125
and fetch_spec is None
2126
and self._has_same_fallbacks(source)):
2127
# check that last_revision is in 'from' and then return a
2129
if (revision_id is not None and
2130
not _mod_revision.is_null(revision_id)):
2131
self.get_revision(revision_id)
2133
# if there is no specific appropriate InterRepository, this will get
2134
# the InterRepository base class, which raises an
2135
# IncompatibleRepositories when asked to fetch.
2136
inter = _mod_repository.InterRepository.get(source, self)
2137
if (fetch_spec is not None and
2138
not getattr(inter, "supports_fetch_spec", False)):
2139
raise errors.UnsupportedOperation(
2140
"fetch_spec not supported for %r" % inter)
2141
return inter.fetch(revision_id=revision_id,
2142
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2144
def create_bundle(self, target, base, fileobj, format=None):
2146
self._real_repository.create_bundle(target, base, fileobj, format)
2149
@symbol_versioning.deprecated_method(
2150
symbol_versioning.deprecated_in((2, 4, 0)))
2151
def get_ancestry(self, revision_id, topo_sorted=True):
2153
return self._real_repository.get_ancestry(revision_id, topo_sorted)
2155
def fileids_altered_by_revision_ids(self, revision_ids):
2157
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2159
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2161
return self._real_repository._get_versioned_file_checker(
2162
revisions, revision_versions_cache)
2164
def _iter_files_bytes_rpc(self, desired_files, absent):
2165
path = self.bzrdir._path_for_remote_call(self._client)
2168
for (file_id, revid, identifier) in desired_files:
2169
lines.append("%s\0%s" % (
2170
osutils.safe_file_id(file_id),
2171
osutils.safe_revision_id(revid)))
2172
identifiers.append(identifier)
2173
(response_tuple, response_handler) = (
2174
self._call_with_body_bytes_expecting_body(
2175
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2176
if response_tuple != ('ok', ):
2177
response_handler.cancel_read_body()
2178
raise errors.UnexpectedSmartServerResponse(response_tuple)
2179
byte_stream = response_handler.read_streamed_body()
2180
def decompress_stream(start, byte_stream, unused):
2181
decompressor = zlib.decompressobj()
2182
yield decompressor.decompress(start)
2183
while decompressor.unused_data == "":
2185
data = byte_stream.next()
2186
except StopIteration:
2188
yield decompressor.decompress(data)
2189
yield decompressor.flush()
2190
unused.append(decompressor.unused_data)
2193
while not "\n" in unused:
2194
unused += byte_stream.next()
2195
header, rest = unused.split("\n", 1)
2196
args = header.split("\0")
2197
if args[0] == "absent":
2198
absent[identifiers[int(args[3])]] = (args[1], args[2])
2201
elif args[0] == "ok":
2204
raise errors.UnexpectedSmartServerResponse(args)
2206
yield (identifiers[idx],
2207
decompress_stream(rest, byte_stream, unused_chunks))
2208
unused = "".join(unused_chunks)
2210
def iter_files_bytes(self, desired_files):
2211
"""See Repository.iter_file_bytes.
2215
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2216
desired_files, absent):
2217
yield identifier, bytes_iterator
2218
for fallback in self._fallback_repositories:
2221
desired_files = [(key[0], key[1], identifier) for
2222
(identifier, key) in absent.iteritems()]
2223
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2224
del absent[identifier]
2225
yield identifier, bytes_iterator
2227
# There may be more missing items, but raise an exception
2229
missing_identifier = absent.keys()[0]
2230
missing_key = absent[missing_identifier]
2231
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2232
file_id=missing_key[0])
2233
except errors.UnknownSmartMethod:
2235
for (identifier, bytes_iterator) in (
2236
self._real_repository.iter_files_bytes(desired_files)):
2237
yield identifier, bytes_iterator
2239
def get_cached_parent_map(self, revision_ids):
2240
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2241
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2243
def get_parent_map(self, revision_ids):
2244
"""See bzrlib.Graph.get_parent_map()."""
2245
return self._make_parents_provider().get_parent_map(revision_ids)
2247
def _get_parent_map_rpc(self, keys):
2248
"""Helper for get_parent_map that performs the RPC."""
2249
medium = self._client._medium
2250
if medium._is_remote_before((1, 2)):
2251
# We already found out that the server can't understand
2252
# Repository.get_parent_map requests, so just fetch the whole
2255
# Note that this reads the whole graph, when only some keys are
2256
# wanted. On this old server there's no way (?) to get them all
2257
# in one go, and the user probably will have seen a warning about
2258
# the server being old anyhow.
2259
rg = self._get_revision_graph(None)
2260
# There is an API discrepancy between get_parent_map and
2261
# get_revision_graph. Specifically, a "key:()" pair in
2262
# get_revision_graph just means a node has no parents. For
2263
# "get_parent_map" it means the node is a ghost. So fix up the
2264
# graph to correct this.
2265
# https://bugs.launchpad.net/bzr/+bug/214894
2266
# There is one other "bug" which is that ghosts in
2267
# get_revision_graph() are not returned at all. But we won't worry
2268
# about that for now.
2269
for node_id, parent_ids in rg.iteritems():
2270
if parent_ids == ():
2271
rg[node_id] = (NULL_REVISION,)
2272
rg[NULL_REVISION] = ()
2277
raise ValueError('get_parent_map(None) is not valid')
2278
if NULL_REVISION in keys:
2279
keys.discard(NULL_REVISION)
2280
found_parents = {NULL_REVISION:()}
2282
return found_parents
2285
# TODO(Needs analysis): We could assume that the keys being requested
2286
# from get_parent_map are in a breadth first search, so typically they
2287
# will all be depth N from some common parent, and we don't have to
2288
# have the server iterate from the root parent, but rather from the
2289
# keys we're searching; and just tell the server the keyspace we
2290
# already have; but this may be more traffic again.
2292
# Transform self._parents_map into a search request recipe.
2293
# TODO: Manage this incrementally to avoid covering the same path
2294
# repeatedly. (The server will have to on each request, but the less
2295
# work done the better).
2297
# Negative caching notes:
2298
# new server sends missing when a request including the revid
2299
# 'include-missing:' is present in the request.
2300
# missing keys are serialised as missing:X, and we then call
2301
# provider.note_missing(X) for-all X
2302
parents_map = self._unstacked_provider.get_cached_map()
2303
if parents_map is None:
2304
# Repository is not locked, so there's no cache.
2306
if _DEFAULT_SEARCH_DEPTH <= 0:
2307
(start_set, stop_keys,
2308
key_count) = vf_search.search_result_from_parent_map(
2309
parents_map, self._unstacked_provider.missing_keys)
2311
(start_set, stop_keys,
2312
key_count) = vf_search.limited_search_result_from_parent_map(
2313
parents_map, self._unstacked_provider.missing_keys,
2314
keys, depth=_DEFAULT_SEARCH_DEPTH)
2315
recipe = ('manual', start_set, stop_keys, key_count)
2316
body = self._serialise_search_recipe(recipe)
2317
path = self.bzrdir._path_for_remote_call(self._client)
2319
if type(key) is not str:
2321
"key %r not a plain string" % (key,))
2322
verb = 'Repository.get_parent_map'
2323
args = (path, 'include-missing:') + tuple(keys)
2325
response = self._call_with_body_bytes_expecting_body(
2327
except errors.UnknownSmartMethod:
2328
# Server does not support this method, so get the whole graph.
2329
# Worse, we have to force a disconnection, because the server now
2330
# doesn't realise it has a body on the wire to consume, so the
2331
# only way to recover is to abandon the connection.
2333
'Server is too old for fast get_parent_map, reconnecting. '
2334
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2336
# To avoid having to disconnect repeatedly, we keep track of the
2337
# fact the server doesn't understand remote methods added in 1.2.
2338
medium._remember_remote_is_before((1, 2))
2339
# Recurse just once and we should use the fallback code.
2340
return self._get_parent_map_rpc(keys)
2341
response_tuple, response_handler = response
2342
if response_tuple[0] not in ['ok']:
2343
response_handler.cancel_read_body()
2344
raise errors.UnexpectedSmartServerResponse(response_tuple)
2345
if response_tuple[0] == 'ok':
2346
coded = bz2.decompress(response_handler.read_body_bytes())
2348
# no revisions found
2350
lines = coded.split('\n')
2353
d = tuple(line.split())
2355
revision_graph[d[0]] = d[1:]
2358
if d[0].startswith('missing:'):
2360
self._unstacked_provider.note_missing_key(revid)
2362
# no parents - so give the Graph result
2364
revision_graph[d[0]] = (NULL_REVISION,)
2365
return revision_graph
2368
def get_signature_text(self, revision_id):
2369
path = self.bzrdir._path_for_remote_call(self._client)
2371
response_tuple, response_handler = self._call_expecting_body(
2372
'Repository.get_revision_signature_text', path, revision_id)
2373
except errors.UnknownSmartMethod:
2375
return self._real_repository.get_signature_text(revision_id)
2376
except errors.NoSuchRevision, err:
2377
for fallback in self._fallback_repositories:
2379
return fallback.get_signature_text(revision_id)
2380
except errors.NoSuchRevision:
2384
if response_tuple[0] != 'ok':
2385
raise errors.UnexpectedSmartServerResponse(response_tuple)
2386
return response_handler.read_body_bytes()
2389
def _get_inventory_xml(self, revision_id):
2390
# This call is used by older working tree formats,
2391
# which stored a serialized basis inventory.
2393
return self._real_repository._get_inventory_xml(revision_id)
2396
def reconcile(self, other=None, thorough=False):
2397
from bzrlib.reconcile import RepoReconciler
2398
path = self.bzrdir._path_for_remote_call(self._client)
2400
response, handler = self._call_expecting_body(
2401
'Repository.reconcile', path, self._lock_token)
2402
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2404
return self._real_repository.reconcile(other=other, thorough=thorough)
2405
if response != ('ok', ):
2406
raise errors.UnexpectedSmartServerResponse(response)
2407
body = handler.read_body_bytes()
2408
result = RepoReconciler(self)
2409
for line in body.split('\n'):
2412
key, val_text = line.split(':')
2413
if key == "garbage_inventories":
2414
result.garbage_inventories = int(val_text)
2415
elif key == "inconsistent_parents":
2416
result.inconsistent_parents = int(val_text)
2418
mutter("unknown reconcile key %r" % key)
2421
def all_revision_ids(self):
2422
path = self.bzrdir._path_for_remote_call(self._client)
2424
response_tuple, response_handler = self._call_expecting_body(
2425
"Repository.all_revision_ids", path)
2426
except errors.UnknownSmartMethod:
2428
return self._real_repository.all_revision_ids()
2429
if response_tuple != ("ok", ):
2430
raise errors.UnexpectedSmartServerResponse(response_tuple)
2431
revids = set(response_handler.read_body_bytes().splitlines())
2432
for fallback in self._fallback_repositories:
2433
revids.update(set(fallback.all_revision_ids()))
2436
def _filtered_revision_trees(self, revision_ids, file_ids):
2437
"""Return Tree for a revision on this branch with only some files.
2439
:param revision_ids: a sequence of revision-ids;
2440
a revision-id may not be None or 'null:'
2441
:param file_ids: if not None, the result is filtered
2442
so that only those file-ids, their parents and their
2443
children are included.
2445
inventories = self.iter_inventories(revision_ids)
2446
for inv in inventories:
2447
# Should we introduce a FilteredRevisionTree class rather
2448
# than pre-filter the inventory here?
2449
filtered_inv = inv.filter(file_ids)
2450
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2453
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2454
medium = self._client._medium
2455
if medium._is_remote_before((1, 2)):
2457
for delta in self._real_repository.get_deltas_for_revisions(
2458
revisions, specific_fileids):
2461
# Get the revision-ids of interest
2462
required_trees = set()
2463
for revision in revisions:
2464
required_trees.add(revision.revision_id)
2465
required_trees.update(revision.parent_ids[:1])
2467
# Get the matching filtered trees. Note that it's more
2468
# efficient to pass filtered trees to changes_from() rather
2469
# than doing the filtering afterwards. changes_from() could
2470
# arguably do the filtering itself but it's path-based, not
2471
# file-id based, so filtering before or afterwards is
2473
if specific_fileids is None:
2474
trees = dict((t.get_revision_id(), t) for
2475
t in self.revision_trees(required_trees))
2477
trees = dict((t.get_revision_id(), t) for
2478
t in self._filtered_revision_trees(required_trees,
2481
# Calculate the deltas
2482
for revision in revisions:
2483
if not revision.parent_ids:
2484
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2486
old_tree = trees[revision.parent_ids[0]]
2487
yield trees[revision.revision_id].changes_from(old_tree)
2490
def get_revision_delta(self, revision_id, specific_fileids=None):
2491
r = self.get_revision(revision_id)
2492
return list(self.get_deltas_for_revisions([r],
2493
specific_fileids=specific_fileids))[0]
2496
def revision_trees(self, revision_ids):
2497
inventories = self.iter_inventories(revision_ids)
2498
for inv in inventories:
2499
yield InventoryRevisionTree(self, inv, inv.revision_id)
2502
def get_revision_reconcile(self, revision_id):
2504
return self._real_repository.get_revision_reconcile(revision_id)
2507
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2509
return self._real_repository.check(revision_ids=revision_ids,
2510
callback_refs=callback_refs, check_repo=check_repo)
2512
def copy_content_into(self, destination, revision_id=None):
2513
"""Make a complete copy of the content in self into destination.
2515
This is a destructive operation! Do not use it on existing
2518
interrepo = _mod_repository.InterRepository.get(self, destination)
2519
return interrepo.copy_content(revision_id)
2521
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2522
# get a tarball of the remote repository, and copy from that into the
2525
# TODO: Maybe a progress bar while streaming the tarball?
2526
note(gettext("Copying repository content as tarball..."))
2527
tar_file = self._get_tarball('bz2')
2528
if tar_file is None:
2530
destination = to_bzrdir.create_repository()
2532
tar = tarfile.open('repository', fileobj=tar_file,
2534
tmpdir = osutils.mkdtemp()
2536
_extract_tar(tar, tmpdir)
2537
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2538
tmp_repo = tmp_bzrdir.open_repository()
2539
tmp_repo.copy_content_into(destination, revision_id)
2541
osutils.rmtree(tmpdir)
2545
# TODO: Suggestion from john: using external tar is much faster than
2546
# python's tarfile library, but it may not work on windows.
2549
def inventories(self):
2550
"""Decorate the real repository for now.
2552
In the long term a full blown network facility is needed to
2553
avoid creating a real repository object locally.
2556
return self._real_repository.inventories
2559
def pack(self, hint=None, clean_obsolete_packs=False):
2560
"""Compress the data within the repository.
2565
body = "".join([l+"\n" for l in hint])
2566
path = self.bzrdir._path_for_remote_call(self._client)
2568
response, handler = self._call_with_body_bytes_expecting_body(
2569
'Repository.pack', (path, self._lock_token,
2570
str(clean_obsolete_packs)), body)
2571
except errors.UnknownSmartMethod:
2573
return self._real_repository.pack(hint=hint,
2574
clean_obsolete_packs=clean_obsolete_packs)
2575
handler.cancel_read_body()
2576
if response != ('ok', ):
2577
raise errors.UnexpectedSmartServerResponse(response)
2580
def revisions(self):
2581
"""Decorate the real repository for now.
2583
In the long term a full blown network facility is needed.
2586
return self._real_repository.revisions
2588
def set_make_working_trees(self, new_value):
2590
new_value_str = "True"
2592
new_value_str = "False"
2593
path = self.bzrdir._path_for_remote_call(self._client)
2595
response = self._call(
2596
'Repository.set_make_working_trees', path, new_value_str)
2597
except errors.UnknownSmartMethod:
2599
self._real_repository.set_make_working_trees(new_value)
2601
if response[0] != 'ok':
2602
raise errors.UnexpectedSmartServerResponse(response)
2605
def signatures(self):
2606
"""Decorate the real repository for now.
2608
In the long term a full blown network facility is needed to avoid
2609
creating a real repository object locally.
2612
return self._real_repository.signatures
2615
def sign_revision(self, revision_id, gpg_strategy):
2616
testament = _mod_testament.Testament.from_revision(self, revision_id)
2617
plaintext = testament.as_short_text()
2618
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2622
"""Decorate the real repository for now.
2624
In the long term a full blown network facility is needed to avoid
2625
creating a real repository object locally.
2628
return self._real_repository.texts
2630
def _iter_revisions_rpc(self, revision_ids):
2631
body = "\n".join(revision_ids)
2632
path = self.bzrdir._path_for_remote_call(self._client)
2633
response_tuple, response_handler = (
2634
self._call_with_body_bytes_expecting_body(
2635
"Repository.iter_revisions", (path, ), body))
2636
if response_tuple[0] != "ok":
2637
raise errors.UnexpectedSmartServerResponse(response_tuple)
2638
serializer_format = response_tuple[1]
2639
serializer = serializer_format_registry.get(serializer_format)
2640
byte_stream = response_handler.read_streamed_body()
2641
decompressor = zlib.decompressobj()
2643
for bytes in byte_stream:
2644
chunks.append(decompressor.decompress(bytes))
2645
if decompressor.unused_data != "":
2646
chunks.append(decompressor.flush())
2647
yield serializer.read_revision_from_string("".join(chunks))
2648
unused = decompressor.unused_data
2649
decompressor = zlib.decompressobj()
2650
chunks = [decompressor.decompress(unused)]
2651
chunks.append(decompressor.flush())
2652
text = "".join(chunks)
2654
yield serializer.read_revision_from_string("".join(chunks))
2657
def get_revisions(self, revision_ids):
2658
if revision_ids is None:
2659
revision_ids = self.all_revision_ids()
2661
for rev_id in revision_ids:
2662
if not rev_id or not isinstance(rev_id, basestring):
2663
raise errors.InvalidRevisionId(
2664
revision_id=rev_id, branch=self)
2666
missing = set(revision_ids)
2668
for rev in self._iter_revisions_rpc(revision_ids):
2669
missing.remove(rev.revision_id)
2670
revs[rev.revision_id] = rev
2671
except errors.UnknownSmartMethod:
2673
return self._real_repository.get_revisions(revision_ids)
2674
for fallback in self._fallback_repositories:
2677
for revid in list(missing):
2678
# XXX JRV 2011-11-20: It would be nice if there was a
2679
# public method on Repository that could be used to query
2680
# for revision objects *without* failing completely if one
2681
# was missing. There is VersionedFileRepository._iter_revisions,
2682
# but unfortunately that's private and not provided by
2683
# all repository implementations.
2685
revs[revid] = fallback.get_revision(revid)
2686
except errors.NoSuchRevision:
2689
missing.remove(revid)
2691
raise errors.NoSuchRevision(self, list(missing)[0])
2692
return [revs[revid] for revid in revision_ids]
2694
def supports_rich_root(self):
2695
return self._format.rich_root_data
2697
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2698
def iter_reverse_revision_history(self, revision_id):
2700
return self._real_repository.iter_reverse_revision_history(revision_id)
2703
def _serializer(self):
2704
return self._format._serializer
2707
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2708
signature = gpg_strategy.sign(plaintext)
2709
self.add_signature_text(revision_id, signature)
2711
def add_signature_text(self, revision_id, signature):
2712
if self._real_repository:
2713
# If there is a real repository the write group will
2714
# be in the real repository as well, so use that:
2716
return self._real_repository.add_signature_text(
2717
revision_id, signature)
2718
path = self.bzrdir._path_for_remote_call(self._client)
2719
response, handler = self._call_with_body_bytes_expecting_body(
2720
'Repository.add_signature_text', (path, self._lock_token,
2721
revision_id) + tuple(self._write_group_tokens), signature)
2722
handler.cancel_read_body()
2724
if response[0] != 'ok':
2725
raise errors.UnexpectedSmartServerResponse(response)
2726
self._write_group_tokens = response[1:]
2728
def has_signature_for_revision_id(self, revision_id):
2729
path = self.bzrdir._path_for_remote_call(self._client)
2731
response = self._call('Repository.has_signature_for_revision_id',
2733
except errors.UnknownSmartMethod:
2735
return self._real_repository.has_signature_for_revision_id(
2737
if response[0] not in ('yes', 'no'):
2738
raise SmartProtocolError('unexpected response code %s' % (response,))
2739
if response[0] == 'yes':
2741
for fallback in self._fallback_repositories:
2742
if fallback.has_signature_for_revision_id(revision_id):
2747
def verify_revision_signature(self, revision_id, gpg_strategy):
2748
if not self.has_signature_for_revision_id(revision_id):
2749
return gpg.SIGNATURE_NOT_SIGNED, None
2750
signature = self.get_signature_text(revision_id)
2752
testament = _mod_testament.Testament.from_revision(self, revision_id)
2753
plaintext = testament.as_short_text()
2755
return gpg_strategy.verify(signature, plaintext)
2757
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2759
return self._real_repository.item_keys_introduced_by(revision_ids,
2760
_files_pb=_files_pb)
2762
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2764
return self._real_repository._find_inconsistent_revision_parents(
2767
def _check_for_inconsistent_revision_parents(self):
2769
return self._real_repository._check_for_inconsistent_revision_parents()
2771
def _make_parents_provider(self, other=None):
2772
providers = [self._unstacked_provider]
2773
if other is not None:
2774
providers.insert(0, other)
2775
return graph.StackedParentsProvider(_LazyListJoin(
2776
providers, self._fallback_repositories))
2778
def _serialise_search_recipe(self, recipe):
2779
"""Serialise a graph search recipe.
2781
:param recipe: A search recipe (start, stop, count).
2782
:return: Serialised bytes.
2784
start_keys = ' '.join(recipe[1])
2785
stop_keys = ' '.join(recipe[2])
2786
count = str(recipe[3])
2787
return '\n'.join((start_keys, stop_keys, count))
2789
def _serialise_search_result(self, search_result):
2790
parts = search_result.get_network_struct()
2791
return '\n'.join(parts)
2794
path = self.bzrdir._path_for_remote_call(self._client)
2796
response = self._call('PackRepository.autopack', path)
2797
except errors.UnknownSmartMethod:
2799
self._real_repository._pack_collection.autopack()
2802
if response[0] != 'ok':
2803
raise errors.UnexpectedSmartServerResponse(response)
2806
class RemoteStreamSink(vf_repository.StreamSink):
2808
def _insert_real(self, stream, src_format, resume_tokens):
2809
self.target_repo._ensure_real()
2810
sink = self.target_repo._real_repository._get_sink()
2811
result = sink.insert_stream(stream, src_format, resume_tokens)
2813
self.target_repo.autopack()
2816
def insert_stream(self, stream, src_format, resume_tokens):
2817
target = self.target_repo
2818
target._unstacked_provider.missing_keys.clear()
2819
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2820
if target._lock_token:
2821
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2822
lock_args = (target._lock_token or '',)
2824
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2826
client = target._client
2827
medium = client._medium
2828
path = target.bzrdir._path_for_remote_call(client)
2829
# Probe for the verb to use with an empty stream before sending the
2830
# real stream to it. We do this both to avoid the risk of sending a
2831
# large request that is then rejected, and because we don't want to
2832
# implement a way to buffer, rewind, or restart the stream.
2834
for verb, required_version in candidate_calls:
2835
if medium._is_remote_before(required_version):
2838
# We've already done the probing (and set _is_remote_before) on
2839
# a previous insert.
2842
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2844
response = client.call_with_body_stream(
2845
(verb, path, '') + lock_args, byte_stream)
2846
except errors.UnknownSmartMethod:
2847
medium._remember_remote_is_before(required_version)
2853
return self._insert_real(stream, src_format, resume_tokens)
2854
self._last_inv_record = None
2855
self._last_substream = None
2856
if required_version < (1, 19):
2857
# Remote side doesn't support inventory deltas. Wrap the stream to
2858
# make sure we don't send any. If the stream contains inventory
2859
# deltas we'll interrupt the smart insert_stream request and
2861
stream = self._stop_stream_if_inventory_delta(stream)
2862
byte_stream = smart_repo._stream_to_byte_stream(
2864
resume_tokens = ' '.join(resume_tokens)
2865
response = client.call_with_body_stream(
2866
(verb, path, resume_tokens) + lock_args, byte_stream)
2867
if response[0][0] not in ('ok', 'missing-basis'):
2868
raise errors.UnexpectedSmartServerResponse(response)
2869
if self._last_substream is not None:
2870
# The stream included an inventory-delta record, but the remote
2871
# side isn't new enough to support them. So we need to send the
2872
# rest of the stream via VFS.
2873
self.target_repo.refresh_data()
2874
return self._resume_stream_with_vfs(response, src_format)
2875
if response[0][0] == 'missing-basis':
2876
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2877
resume_tokens = tokens
2878
return resume_tokens, set(missing_keys)
2880
self.target_repo.refresh_data()
2883
def _resume_stream_with_vfs(self, response, src_format):
2884
"""Resume sending a stream via VFS, first resending the record and
2885
substream that couldn't be sent via an insert_stream verb.
2887
if response[0][0] == 'missing-basis':
2888
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2889
# Ignore missing_keys, we haven't finished inserting yet
2892
def resume_substream():
2893
# Yield the substream that was interrupted.
2894
for record in self._last_substream:
2896
self._last_substream = None
2897
def resume_stream():
2898
# Finish sending the interrupted substream
2899
yield ('inventory-deltas', resume_substream())
2900
# Then simply continue sending the rest of the stream.
2901
for substream_kind, substream in self._last_stream:
2902
yield substream_kind, substream
2903
return self._insert_real(resume_stream(), src_format, tokens)
2905
def _stop_stream_if_inventory_delta(self, stream):
2906
"""Normally this just lets the original stream pass-through unchanged.
2908
However if any 'inventory-deltas' substream occurs it will stop
2909
streaming, and store the interrupted substream and stream in
2910
self._last_substream and self._last_stream so that the stream can be
2911
resumed by _resume_stream_with_vfs.
2914
stream_iter = iter(stream)
2915
for substream_kind, substream in stream_iter:
2916
if substream_kind == 'inventory-deltas':
2917
self._last_substream = substream
2918
self._last_stream = stream_iter
2921
yield substream_kind, substream
2924
class RemoteStreamSource(vf_repository.StreamSource):
2925
"""Stream data from a remote server."""
2927
def get_stream(self, search):
2928
if (self.from_repository._fallback_repositories and
2929
self.to_format._fetch_order == 'topological'):
2930
return self._real_stream(self.from_repository, search)
2933
repos = [self.from_repository]
2939
repos.extend(repo._fallback_repositories)
2940
sources.append(repo)
2941
return self.missing_parents_chain(search, sources)
2943
def get_stream_for_missing_keys(self, missing_keys):
2944
self.from_repository._ensure_real()
2945
real_repo = self.from_repository._real_repository
2946
real_source = real_repo._get_source(self.to_format)
2947
return real_source.get_stream_for_missing_keys(missing_keys)
2949
def _real_stream(self, repo, search):
2950
"""Get a stream for search from repo.
2952
This never called RemoteStreamSource.get_stream, and is a helper
2953
for RemoteStreamSource._get_stream to allow getting a stream
2954
reliably whether fallback back because of old servers or trying
2955
to stream from a non-RemoteRepository (which the stacked support
2958
source = repo._get_source(self.to_format)
2959
if isinstance(source, RemoteStreamSource):
2961
source = repo._real_repository._get_source(self.to_format)
2962
return source.get_stream(search)
2964
def _get_stream(self, repo, search):
2965
"""Core worker to get a stream from repo for search.
2967
This is used by both get_stream and the stacking support logic. It
2968
deliberately gets a stream for repo which does not need to be
2969
self.from_repository. In the event that repo is not Remote, or
2970
cannot do a smart stream, a fallback is made to the generic
2971
repository._get_stream() interface, via self._real_stream.
2973
In the event of stacking, streams from _get_stream will not
2974
contain all the data for search - this is normal (see get_stream).
2976
:param repo: A repository.
2977
:param search: A search.
2979
# Fallbacks may be non-smart
2980
if not isinstance(repo, RemoteRepository):
2981
return self._real_stream(repo, search)
2982
client = repo._client
2983
medium = client._medium
2984
path = repo.bzrdir._path_for_remote_call(client)
2985
search_bytes = repo._serialise_search_result(search)
2986
args = (path, self.to_format.network_name())
2988
('Repository.get_stream_1.19', (1, 19)),
2989
('Repository.get_stream', (1, 13))]
2992
for verb, version in candidate_verbs:
2993
if medium._is_remote_before(version):
2996
response = repo._call_with_body_bytes_expecting_body(
2997
verb, args, search_bytes)
2998
except errors.UnknownSmartMethod:
2999
medium._remember_remote_is_before(version)
3000
except errors.UnknownErrorFromSmartServer, e:
3001
if isinstance(search, vf_search.EverythingResult):
3002
error_verb = e.error_from_smart_server.error_verb
3003
if error_verb == 'BadSearch':
3004
# Pre-2.4 servers don't support this sort of search.
3005
# XXX: perhaps falling back to VFS on BadSearch is a
3006
# good idea in general? It might provide a little bit
3007
# of protection against client-side bugs.
3008
medium._remember_remote_is_before((2, 4))
3012
response_tuple, response_handler = response
3016
return self._real_stream(repo, search)
3017
if response_tuple[0] != 'ok':
3018
raise errors.UnexpectedSmartServerResponse(response_tuple)
3019
byte_stream = response_handler.read_streamed_body()
3020
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3021
self._record_counter)
3022
if src_format.network_name() != repo._format.network_name():
3023
raise AssertionError(
3024
"Mismatched RemoteRepository and stream src %r, %r" % (
3025
src_format.network_name(), repo._format.network_name()))
3028
def missing_parents_chain(self, search, sources):
3029
"""Chain multiple streams together to handle stacking.
3031
:param search: The overall search to satisfy with streams.
3032
:param sources: A list of Repository objects to query.
3034
self.from_serialiser = self.from_repository._format._serializer
3035
self.seen_revs = set()
3036
self.referenced_revs = set()
3037
# If there are heads in the search, or the key count is > 0, we are not
3039
while not search.is_empty() and len(sources) > 1:
3040
source = sources.pop(0)
3041
stream = self._get_stream(source, search)
3042
for kind, substream in stream:
3043
if kind != 'revisions':
3044
yield kind, substream
3046
yield kind, self.missing_parents_rev_handler(substream)
3047
search = search.refine(self.seen_revs, self.referenced_revs)
3048
self.seen_revs = set()
3049
self.referenced_revs = set()
3050
if not search.is_empty():
3051
for kind, stream in self._get_stream(sources[0], search):
3054
def missing_parents_rev_handler(self, substream):
3055
for content in substream:
3056
revision_bytes = content.get_bytes_as('fulltext')
3057
revision = self.from_serialiser.read_revision_from_string(
3059
self.seen_revs.add(content.key[-1])
3060
self.referenced_revs.update(revision.parent_ids)
3064
class RemoteBranchLockableFiles(LockableFiles):
3065
"""A 'LockableFiles' implementation that talks to a smart server.
3067
This is not a public interface class.
3070
def __init__(self, bzrdir, _client):
3071
self.bzrdir = bzrdir
3072
self._client = _client
3073
self._need_find_modes = True
3074
LockableFiles.__init__(
3075
self, bzrdir.get_branch_transport(None),
3076
'lock', lockdir.LockDir)
3078
def _find_modes(self):
3079
# RemoteBranches don't let the client set the mode of control files.
3080
self._dir_mode = None
3081
self._file_mode = None
3084
class RemoteBranchFormat(branch.BranchFormat):
3086
def __init__(self, network_name=None):
3087
super(RemoteBranchFormat, self).__init__()
3088
self._matchingbzrdir = RemoteBzrDirFormat()
3089
self._matchingbzrdir.set_branch_format(self)
3090
self._custom_format = None
3091
self._network_name = network_name
3093
def __eq__(self, other):
3094
return (isinstance(other, RemoteBranchFormat) and
3095
self.__dict__ == other.__dict__)
3097
def _ensure_real(self):
3098
if self._custom_format is None:
3100
self._custom_format = branch.network_format_registry.get(
3103
raise errors.UnknownFormatError(kind='branch',
3104
format=self._network_name)
3106
def get_format_description(self):
3108
return 'Remote: ' + self._custom_format.get_format_description()
3110
def network_name(self):
3111
return self._network_name
3113
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3114
return a_bzrdir.open_branch(name=name,
3115
ignore_fallbacks=ignore_fallbacks)
3117
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3118
# Initialisation when using a local bzrdir object, or a non-vfs init
3119
# method is not available on the server.
3120
# self._custom_format is always set - the start of initialize ensures
3122
if isinstance(a_bzrdir, RemoteBzrDir):
3123
a_bzrdir._ensure_real()
3124
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3125
name, append_revisions_only=append_revisions_only)
3127
# We assume the bzrdir is parameterised; it may not be.
3128
result = self._custom_format.initialize(a_bzrdir, name,
3129
append_revisions_only=append_revisions_only)
3130
if (isinstance(a_bzrdir, RemoteBzrDir) and
3131
not isinstance(result, RemoteBranch)):
3132
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3136
def initialize(self, a_bzrdir, name=None, repository=None,
3137
append_revisions_only=None):
3139
name = a_bzrdir._get_selected_branch()
3140
# 1) get the network name to use.
3141
if self._custom_format:
3142
network_name = self._custom_format.network_name()
3144
# Select the current bzrlib default and ask for that.
3145
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3146
reference_format = reference_bzrdir_format.get_branch_format()
3147
self._custom_format = reference_format
3148
network_name = reference_format.network_name()
3149
# Being asked to create on a non RemoteBzrDir:
3150
if not isinstance(a_bzrdir, RemoteBzrDir):
3151
return self._vfs_initialize(a_bzrdir, name=name,
3152
append_revisions_only=append_revisions_only)
3153
medium = a_bzrdir._client._medium
3154
if medium._is_remote_before((1, 13)):
3155
return self._vfs_initialize(a_bzrdir, name=name,
3156
append_revisions_only=append_revisions_only)
3157
# Creating on a remote bzr dir.
3158
# 2) try direct creation via RPC
3159
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3161
# XXX JRV20100304: Support creating colocated branches
3162
raise errors.NoColocatedBranchSupport(self)
3163
verb = 'BzrDir.create_branch'
3165
response = a_bzrdir._call(verb, path, network_name)
3166
except errors.UnknownSmartMethod:
3167
# Fallback - use vfs methods
3168
medium._remember_remote_is_before((1, 13))
3169
return self._vfs_initialize(a_bzrdir, name=name,
3170
append_revisions_only=append_revisions_only)
3171
if response[0] != 'ok':
3172
raise errors.UnexpectedSmartServerResponse(response)
3173
# Turn the response into a RemoteRepository object.
3174
format = RemoteBranchFormat(network_name=response[1])
3175
repo_format = response_tuple_to_repo_format(response[3:])
3176
repo_path = response[2]
3177
if repository is not None:
3178
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3179
url_diff = urlutils.relative_url(repository.user_url,
3182
raise AssertionError(
3183
'repository.user_url %r does not match URL from server '
3184
'response (%r + %r)'
3185
% (repository.user_url, a_bzrdir.user_url, repo_path))
3186
remote_repo = repository
3189
repo_bzrdir = a_bzrdir
3191
repo_bzrdir = RemoteBzrDir(
3192
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3194
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3195
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3196
format=format, setup_stacking=False, name=name)
3197
if append_revisions_only:
3198
remote_branch.set_append_revisions_only(append_revisions_only)
3199
# XXX: We know this is a new branch, so it must have revno 0, revid
3200
# NULL_REVISION. Creating the branch locked would make this be unable
3201
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3202
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3203
return remote_branch
3205
def make_tags(self, branch):
3207
return self._custom_format.make_tags(branch)
3209
def supports_tags(self):
3210
# Remote branches might support tags, but we won't know until we
3211
# access the real remote branch.
3213
return self._custom_format.supports_tags()
3215
def supports_stacking(self):
3217
return self._custom_format.supports_stacking()
3219
def supports_set_append_revisions_only(self):
3221
return self._custom_format.supports_set_append_revisions_only()
3223
def _use_default_local_heads_to_fetch(self):
3224
# If the branch format is a metadir format *and* its heads_to_fetch
3225
# implementation is not overridden vs the base class, we can use the
3226
# base class logic rather than use the heads_to_fetch RPC. This is
3227
# usually cheaper in terms of net round trips, as the last-revision and
3228
# tags info fetched is cached and would be fetched anyway.
3230
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3231
branch_class = self._custom_format._branch_class()
3232
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3233
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3238
class RemoteBranchStore(_mod_config.IniFileStore):
3239
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3241
Note that this is specific to bzr-based formats.
3244
def __init__(self, branch):
3245
super(RemoteBranchStore, self).__init__()
3246
self.branch = branch
3248
self._real_store = None
3250
def lock_write(self, token=None):
3251
return self.branch.lock_write(token)
3254
return self.branch.unlock()
3258
# We need to be able to override the undecorated implementation
3259
self.save_without_locking()
3261
def save_without_locking(self):
3262
super(RemoteBranchStore, self).save()
3264
def external_url(self):
3265
return self.branch.user_url
3267
def _load_content(self):
3268
path = self.branch._remote_path()
3270
response, handler = self.branch._call_expecting_body(
3271
'Branch.get_config_file', path)
3272
except errors.UnknownSmartMethod:
3274
return self._real_store._load_content()
3275
if len(response) and response[0] != 'ok':
3276
raise errors.UnexpectedSmartServerResponse(response)
3277
return handler.read_body_bytes()
3279
def _save_content(self, content):
3280
path = self.branch._remote_path()
3282
response, handler = self.branch._call_with_body_bytes_expecting_body(
3283
'Branch.put_config_file', (path,
3284
self.branch._lock_token, self.branch._repo_lock_token),
3286
except errors.UnknownSmartMethod:
3288
return self._real_store._save_content(content)
3289
handler.cancel_read_body()
3290
if response != ('ok', ):
3291
raise errors.UnexpectedSmartServerResponse(response)
3293
def _ensure_real(self):
3294
self.branch._ensure_real()
3295
if self._real_store is None:
3296
self._real_store = _mod_config.BranchStore(self.branch)
3299
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3300
"""Branch stored on a server accessed by HPSS RPC.
3302
At the moment most operations are mapped down to simple file operations.
3305
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3306
_client=None, format=None, setup_stacking=True, name=None,
3307
possible_transports=None):
3308
"""Create a RemoteBranch instance.
3310
:param real_branch: An optional local implementation of the branch
3311
format, usually accessing the data via the VFS.
3312
:param _client: Private parameter for testing.
3313
:param format: A RemoteBranchFormat object, None to create one
3314
automatically. If supplied it should have a network_name already
3316
:param setup_stacking: If True make an RPC call to determine the
3317
stacked (or not) status of the branch. If False assume the branch
3319
:param name: Colocated branch name
3321
# We intentionally don't call the parent class's __init__, because it
3322
# will try to assign to self.tags, which is a property in this subclass.
3323
# And the parent's __init__ doesn't do much anyway.
3324
self.bzrdir = remote_bzrdir
3325
if _client is not None:
3326
self._client = _client
3328
self._client = remote_bzrdir._client
3329
self.repository = remote_repository
3330
if real_branch is not None:
3331
self._real_branch = real_branch
3332
# Give the remote repository the matching real repo.
3333
real_repo = self._real_branch.repository
3334
if isinstance(real_repo, RemoteRepository):
3335
real_repo._ensure_real()
3336
real_repo = real_repo._real_repository
3337
self.repository._set_real_repository(real_repo)
3338
# Give the branch the remote repository to let fast-pathing happen.
3339
self._real_branch.repository = self.repository
3341
self._real_branch = None
3342
# Fill out expected attributes of branch for bzrlib API users.
3343
self._clear_cached_state()
3344
# TODO: deprecate self.base in favor of user_url
3345
self.base = self.bzrdir.user_url
3347
self._control_files = None
3348
self._lock_mode = None
3349
self._lock_token = None
3350
self._repo_lock_token = None
3351
self._lock_count = 0
3352
self._leave_lock = False
3353
# Setup a format: note that we cannot call _ensure_real until all the
3354
# attributes above are set: This code cannot be moved higher up in this
3357
self._format = RemoteBranchFormat()
3358
if real_branch is not None:
3359
self._format._network_name = \
3360
self._real_branch._format.network_name()
3362
self._format = format
3363
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3364
# branch.open_branch method.
3365
self._real_ignore_fallbacks = not setup_stacking
3366
if not self._format._network_name:
3367
# Did not get from open_branchV2 - old server.
3369
self._format._network_name = \
3370
self._real_branch._format.network_name()
3371
self.tags = self._format.make_tags(self)
3372
# The base class init is not called, so we duplicate this:
3373
hooks = branch.Branch.hooks['open']
3376
self._is_stacked = False
3378
self._setup_stacking(possible_transports)
3380
def _setup_stacking(self, possible_transports):
3381
# configure stacking into the remote repository, by reading it from
3384
fallback_url = self.get_stacked_on_url()
3385
except (errors.NotStacked, errors.UnstackableBranchFormat,
3386
errors.UnstackableRepositoryFormat), e:
3388
self._is_stacked = True
3389
if possible_transports is None:
3390
possible_transports = []
3392
possible_transports = list(possible_transports)
3393
possible_transports.append(self.bzrdir.root_transport)
3394
self._activate_fallback_location(fallback_url,
3395
possible_transports=possible_transports)
3397
def _get_config(self):
3398
return RemoteBranchConfig(self)
3400
def _get_config_store(self):
3401
return RemoteBranchStore(self)
3403
def _get_real_transport(self):
3404
# if we try vfs access, return the real branch's vfs transport
3406
return self._real_branch._transport
3408
_transport = property(_get_real_transport)
3411
return "%s(%s)" % (self.__class__.__name__, self.base)
3415
def _ensure_real(self):
3416
"""Ensure that there is a _real_branch set.
3418
Used before calls to self._real_branch.
3420
if self._real_branch is None:
3421
if not vfs.vfs_enabled():
3422
raise AssertionError('smart server vfs must be enabled '
3423
'to use vfs implementation')
3424
self.bzrdir._ensure_real()
3425
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3426
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3427
if self.repository._real_repository is None:
3428
# Give the remote repository the matching real repo.
3429
real_repo = self._real_branch.repository
3430
if isinstance(real_repo, RemoteRepository):
3431
real_repo._ensure_real()
3432
real_repo = real_repo._real_repository
3433
self.repository._set_real_repository(real_repo)
3434
# Give the real branch the remote repository to let fast-pathing
3436
self._real_branch.repository = self.repository
3437
if self._lock_mode == 'r':
3438
self._real_branch.lock_read()
3439
elif self._lock_mode == 'w':
3440
self._real_branch.lock_write(token=self._lock_token)
3442
def _translate_error(self, err, **context):
3443
self.repository._translate_error(err, branch=self, **context)
3445
def _clear_cached_state(self):
3446
super(RemoteBranch, self)._clear_cached_state()
3447
if self._real_branch is not None:
3448
self._real_branch._clear_cached_state()
3450
def _clear_cached_state_of_remote_branch_only(self):
3451
"""Like _clear_cached_state, but doesn't clear the cache of
3454
This is useful when falling back to calling a method of
3455
self._real_branch that changes state. In that case the underlying
3456
branch changes, so we need to invalidate this RemoteBranch's cache of
3457
it. However, there's no need to invalidate the _real_branch's cache
3458
too, in fact doing so might harm performance.
3460
super(RemoteBranch, self)._clear_cached_state()
3463
def control_files(self):
3464
# Defer actually creating RemoteBranchLockableFiles until its needed,
3465
# because it triggers an _ensure_real that we otherwise might not need.
3466
if self._control_files is None:
3467
self._control_files = RemoteBranchLockableFiles(
3468
self.bzrdir, self._client)
3469
return self._control_files
3471
def get_physical_lock_status(self):
3472
"""See Branch.get_physical_lock_status()."""
3474
response = self._client.call('Branch.get_physical_lock_status',
3475
self._remote_path())
3476
except errors.UnknownSmartMethod:
3478
return self._real_branch.get_physical_lock_status()
3479
if response[0] not in ('yes', 'no'):
3480
raise errors.UnexpectedSmartServerResponse(response)
3481
return (response[0] == 'yes')
3483
def get_stacked_on_url(self):
3484
"""Get the URL this branch is stacked against.
3486
:raises NotStacked: If the branch is not stacked.
3487
:raises UnstackableBranchFormat: If the branch does not support
3489
:raises UnstackableRepositoryFormat: If the repository does not support
3493
# there may not be a repository yet, so we can't use
3494
# self._translate_error, so we can't use self._call either.
3495
response = self._client.call('Branch.get_stacked_on_url',
3496
self._remote_path())
3497
except errors.ErrorFromSmartServer, err:
3498
# there may not be a repository yet, so we can't call through
3499
# its _translate_error
3500
_translate_error(err, branch=self)
3501
except errors.UnknownSmartMethod, err:
3503
return self._real_branch.get_stacked_on_url()
3504
if response[0] != 'ok':
3505
raise errors.UnexpectedSmartServerResponse(response)
3508
def set_stacked_on_url(self, url):
3509
branch.Branch.set_stacked_on_url(self, url)
3511
self._is_stacked = False
3513
self._is_stacked = True
3515
def _vfs_get_tags_bytes(self):
3517
return self._real_branch._get_tags_bytes()
3520
def _get_tags_bytes(self):
3521
if self._tags_bytes is None:
3522
self._tags_bytes = self._get_tags_bytes_via_hpss()
3523
return self._tags_bytes
3525
def _get_tags_bytes_via_hpss(self):
3526
medium = self._client._medium
3527
if medium._is_remote_before((1, 13)):
3528
return self._vfs_get_tags_bytes()
3530
response = self._call('Branch.get_tags_bytes', self._remote_path())
3531
except errors.UnknownSmartMethod:
3532
medium._remember_remote_is_before((1, 13))
3533
return self._vfs_get_tags_bytes()
3536
def _vfs_set_tags_bytes(self, bytes):
3538
return self._real_branch._set_tags_bytes(bytes)
3540
def _set_tags_bytes(self, bytes):
3541
if self.is_locked():
3542
self._tags_bytes = bytes
3543
medium = self._client._medium
3544
if medium._is_remote_before((1, 18)):
3545
self._vfs_set_tags_bytes(bytes)
3549
self._remote_path(), self._lock_token, self._repo_lock_token)
3550
response = self._call_with_body_bytes(
3551
'Branch.set_tags_bytes', args, bytes)
3552
except errors.UnknownSmartMethod:
3553
medium._remember_remote_is_before((1, 18))
3554
self._vfs_set_tags_bytes(bytes)
3556
def lock_read(self):
3557
"""Lock the branch for read operations.
3559
:return: A bzrlib.lock.LogicalLockResult.
3561
self.repository.lock_read()
3562
if not self._lock_mode:
3563
self._note_lock('r')
3564
self._lock_mode = 'r'
3565
self._lock_count = 1
3566
if self._real_branch is not None:
3567
self._real_branch.lock_read()
3569
self._lock_count += 1
3570
return lock.LogicalLockResult(self.unlock)
3572
def _remote_lock_write(self, token):
3574
branch_token = repo_token = ''
3576
branch_token = token
3577
repo_token = self.repository.lock_write().repository_token
3578
self.repository.unlock()
3579
err_context = {'token': token}
3581
response = self._call(
3582
'Branch.lock_write', self._remote_path(), branch_token,
3583
repo_token or '', **err_context)
3584
except errors.LockContention, e:
3585
# The LockContention from the server doesn't have any
3586
# information about the lock_url. We re-raise LockContention
3587
# with valid lock_url.
3588
raise errors.LockContention('(remote lock)',
3589
self.repository.base.split('.bzr/')[0])
3590
if response[0] != 'ok':
3591
raise errors.UnexpectedSmartServerResponse(response)
3592
ok, branch_token, repo_token = response
3593
return branch_token, repo_token
3595
def lock_write(self, token=None):
3596
if not self._lock_mode:
3597
self._note_lock('w')
3598
# Lock the branch and repo in one remote call.
3599
remote_tokens = self._remote_lock_write(token)
3600
self._lock_token, self._repo_lock_token = remote_tokens
3601
if not self._lock_token:
3602
raise SmartProtocolError('Remote server did not return a token!')
3603
# Tell the self.repository object that it is locked.
3604
self.repository.lock_write(
3605
self._repo_lock_token, _skip_rpc=True)
3607
if self._real_branch is not None:
3608
self._real_branch.lock_write(token=self._lock_token)
3609
if token is not None:
3610
self._leave_lock = True
3612
self._leave_lock = False
3613
self._lock_mode = 'w'
3614
self._lock_count = 1
3615
elif self._lock_mode == 'r':
3616
raise errors.ReadOnlyError(self)
3618
if token is not None:
3619
# A token was given to lock_write, and we're relocking, so
3620
# check that the given token actually matches the one we
3622
if token != self._lock_token:
3623
raise errors.TokenMismatch(token, self._lock_token)
3624
self._lock_count += 1
3625
# Re-lock the repository too.
3626
self.repository.lock_write(self._repo_lock_token)
3627
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3629
def _unlock(self, branch_token, repo_token):
3630
err_context = {'token': str((branch_token, repo_token))}
3631
response = self._call(
3632
'Branch.unlock', self._remote_path(), branch_token,
3633
repo_token or '', **err_context)
3634
if response == ('ok',):
3636
raise errors.UnexpectedSmartServerResponse(response)
3638
@only_raises(errors.LockNotHeld, errors.LockBroken)
3641
self._lock_count -= 1
3642
if not self._lock_count:
3643
self._clear_cached_state()
3644
mode = self._lock_mode
3645
self._lock_mode = None
3646
if self._real_branch is not None:
3647
if (not self._leave_lock and mode == 'w' and
3648
self._repo_lock_token):
3649
# If this RemoteBranch will remove the physical lock
3650
# for the repository, make sure the _real_branch
3651
# doesn't do it first. (Because the _real_branch's
3652
# repository is set to be the RemoteRepository.)
3653
self._real_branch.repository.leave_lock_in_place()
3654
self._real_branch.unlock()
3656
# Only write-locked branched need to make a remote method
3657
# call to perform the unlock.
3659
if not self._lock_token:
3660
raise AssertionError('Locked, but no token!')
3661
branch_token = self._lock_token
3662
repo_token = self._repo_lock_token
3663
self._lock_token = None
3664
self._repo_lock_token = None
3665
if not self._leave_lock:
3666
self._unlock(branch_token, repo_token)
3668
self.repository.unlock()
3670
def break_lock(self):
3672
response = self._call(
3673
'Branch.break_lock', self._remote_path())
3674
except errors.UnknownSmartMethod:
3676
return self._real_branch.break_lock()
3677
if response != ('ok',):
3678
raise errors.UnexpectedSmartServerResponse(response)
3680
def leave_lock_in_place(self):
3681
if not self._lock_token:
3682
raise NotImplementedError(self.leave_lock_in_place)
3683
self._leave_lock = True
3685
def dont_leave_lock_in_place(self):
3686
if not self._lock_token:
3687
raise NotImplementedError(self.dont_leave_lock_in_place)
3688
self._leave_lock = False
3691
def get_rev_id(self, revno, history=None):
3693
return _mod_revision.NULL_REVISION
3694
last_revision_info = self.last_revision_info()
3695
ok, result = self.repository.get_rev_id_for_revno(
3696
revno, last_revision_info)
3699
missing_parent = result[1]
3700
# Either the revision named by the server is missing, or its parent
3701
# is. Call get_parent_map to determine which, so that we report a
3703
parent_map = self.repository.get_parent_map([missing_parent])
3704
if missing_parent in parent_map:
3705
missing_parent = parent_map[missing_parent]
3706
raise errors.RevisionNotPresent(missing_parent, self.repository)
3708
def _read_last_revision_info(self):
3709
response = self._call('Branch.last_revision_info', self._remote_path())
3710
if response[0] != 'ok':
3711
raise SmartProtocolError('unexpected response code %s' % (response,))
3712
revno = int(response[1])
3713
last_revision = response[2]
3714
return (revno, last_revision)
3716
def _gen_revision_history(self):
3717
"""See Branch._gen_revision_history()."""
3718
if self._is_stacked:
3720
return self._real_branch._gen_revision_history()
3721
response_tuple, response_handler = self._call_expecting_body(
3722
'Branch.revision_history', self._remote_path())
3723
if response_tuple[0] != 'ok':
3724
raise errors.UnexpectedSmartServerResponse(response_tuple)
3725
result = response_handler.read_body_bytes().split('\x00')
3730
def _remote_path(self):
3731
return self.bzrdir._path_for_remote_call(self._client)
3733
def _set_last_revision_descendant(self, revision_id, other_branch,
3734
allow_diverged=False, allow_overwrite_descendant=False):
3735
# This performs additional work to meet the hook contract; while its
3736
# undesirable, we have to synthesise the revno to call the hook, and
3737
# not calling the hook is worse as it means changes can't be prevented.
3738
# Having calculated this though, we can't just call into
3739
# set_last_revision_info as a simple call, because there is a set_rh
3740
# hook that some folk may still be using.
3741
old_revno, old_revid = self.last_revision_info()
3742
history = self._lefthand_history(revision_id)
3743
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3744
err_context = {'other_branch': other_branch}
3745
response = self._call('Branch.set_last_revision_ex',
3746
self._remote_path(), self._lock_token, self._repo_lock_token,
3747
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3749
self._clear_cached_state()
3750
if len(response) != 3 and response[0] != 'ok':
3751
raise errors.UnexpectedSmartServerResponse(response)
3752
new_revno, new_revision_id = response[1:]
3753
self._last_revision_info_cache = new_revno, new_revision_id
3754
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3755
if self._real_branch is not None:
3756
cache = new_revno, new_revision_id
3757
self._real_branch._last_revision_info_cache = cache
3759
def _set_last_revision(self, revision_id):
3760
old_revno, old_revid = self.last_revision_info()
3761
# This performs additional work to meet the hook contract; while its
3762
# undesirable, we have to synthesise the revno to call the hook, and
3763
# not calling the hook is worse as it means changes can't be prevented.
3764
# Having calculated this though, we can't just call into
3765
# set_last_revision_info as a simple call, because there is a set_rh
3766
# hook that some folk may still be using.
3767
history = self._lefthand_history(revision_id)
3768
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3769
self._clear_cached_state()
3770
response = self._call('Branch.set_last_revision',
3771
self._remote_path(), self._lock_token, self._repo_lock_token,
3773
if response != ('ok',):
3774
raise errors.UnexpectedSmartServerResponse(response)
3775
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3777
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3779
def set_revision_history(self, rev_history):
3780
"""See Branch.set_revision_history."""
3781
self._set_revision_history(rev_history)
3784
def _set_revision_history(self, rev_history):
3785
# Send just the tip revision of the history; the server will generate
3786
# the full history from that. If the revision doesn't exist in this
3787
# branch, NoSuchRevision will be raised.
3788
if rev_history == []:
3791
rev_id = rev_history[-1]
3792
self._set_last_revision(rev_id)
3793
for hook in branch.Branch.hooks['set_rh']:
3794
hook(self, rev_history)
3795
self._cache_revision_history(rev_history)
3797
def _get_parent_location(self):
3798
medium = self._client._medium
3799
if medium._is_remote_before((1, 13)):
3800
return self._vfs_get_parent_location()
3802
response = self._call('Branch.get_parent', self._remote_path())
3803
except errors.UnknownSmartMethod:
3804
medium._remember_remote_is_before((1, 13))
3805
return self._vfs_get_parent_location()
3806
if len(response) != 1:
3807
raise errors.UnexpectedSmartServerResponse(response)
3808
parent_location = response[0]
3809
if parent_location == '':
3811
return parent_location
3813
def _vfs_get_parent_location(self):
3815
return self._real_branch._get_parent_location()
3817
def _set_parent_location(self, url):
3818
medium = self._client._medium
3819
if medium._is_remote_before((1, 15)):
3820
return self._vfs_set_parent_location(url)
3822
call_url = url or ''
3823
if type(call_url) is not str:
3824
raise AssertionError('url must be a str or None (%s)' % url)
3825
response = self._call('Branch.set_parent_location',
3826
self._remote_path(), self._lock_token, self._repo_lock_token,
3828
except errors.UnknownSmartMethod:
3829
medium._remember_remote_is_before((1, 15))
3830
return self._vfs_set_parent_location(url)
3832
raise errors.UnexpectedSmartServerResponse(response)
3834
def _vfs_set_parent_location(self, url):
3836
return self._real_branch._set_parent_location(url)
3839
def pull(self, source, overwrite=False, stop_revision=None,
3841
self._clear_cached_state_of_remote_branch_only()
3843
return self._real_branch.pull(
3844
source, overwrite=overwrite, stop_revision=stop_revision,
3845
_override_hook_target=self, **kwargs)
3848
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3850
return self._real_branch.push(
3851
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3852
_override_hook_source_branch=self)
3854
def is_locked(self):
3855
return self._lock_count >= 1
3858
def revision_id_to_dotted_revno(self, revision_id):
3859
"""Given a revision id, return its dotted revno.
3861
:return: a tuple like (1,) or (400,1,3).
3864
response = self._call('Branch.revision_id_to_revno',
3865
self._remote_path(), revision_id)
3866
except errors.UnknownSmartMethod:
3868
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3869
if response[0] == 'ok':
3870
return tuple([int(x) for x in response[1:]])
3872
raise errors.UnexpectedSmartServerResponse(response)
3875
def revision_id_to_revno(self, revision_id):
3876
"""Given a revision id on the branch mainline, return its revno.
3881
response = self._call('Branch.revision_id_to_revno',
3882
self._remote_path(), revision_id)
3883
except errors.UnknownSmartMethod:
3885
return self._real_branch.revision_id_to_revno(revision_id)
3886
if response[0] == 'ok':
3887
if len(response) == 2:
3888
return int(response[1])
3889
raise NoSuchRevision(self, revision_id)
3891
raise errors.UnexpectedSmartServerResponse(response)
3894
def set_last_revision_info(self, revno, revision_id):
3895
# XXX: These should be returned by the set_last_revision_info verb
3896
old_revno, old_revid = self.last_revision_info()
3897
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3898
if not revision_id or not isinstance(revision_id, basestring):
3899
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3901
response = self._call('Branch.set_last_revision_info',
3902
self._remote_path(), self._lock_token, self._repo_lock_token,
3903
str(revno), revision_id)
3904
except errors.UnknownSmartMethod:
3906
self._clear_cached_state_of_remote_branch_only()
3907
self._real_branch.set_last_revision_info(revno, revision_id)
3908
self._last_revision_info_cache = revno, revision_id
3910
if response == ('ok',):
3911
self._clear_cached_state()
3912
self._last_revision_info_cache = revno, revision_id
3913
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3914
# Update the _real_branch's cache too.
3915
if self._real_branch is not None:
3916
cache = self._last_revision_info_cache
3917
self._real_branch._last_revision_info_cache = cache
3919
raise errors.UnexpectedSmartServerResponse(response)
3922
def generate_revision_history(self, revision_id, last_rev=None,
3924
medium = self._client._medium
3925
if not medium._is_remote_before((1, 6)):
3926
# Use a smart method for 1.6 and above servers
3928
self._set_last_revision_descendant(revision_id, other_branch,
3929
allow_diverged=True, allow_overwrite_descendant=True)
3931
except errors.UnknownSmartMethod:
3932
medium._remember_remote_is_before((1, 6))
3933
self._clear_cached_state_of_remote_branch_only()
3934
self._set_revision_history(self._lefthand_history(revision_id,
3935
last_rev=last_rev,other_branch=other_branch))
3937
def set_push_location(self, location):
3939
return self._real_branch.set_push_location(location)
3941
def heads_to_fetch(self):
3942
if self._format._use_default_local_heads_to_fetch():
3943
# We recognise this format, and its heads-to-fetch implementation
3944
# is the default one (tip + tags). In this case it's cheaper to
3945
# just use the default implementation rather than a special RPC as
3946
# the tip and tags data is cached.
3947
return branch.Branch.heads_to_fetch(self)
3948
medium = self._client._medium
3949
if medium._is_remote_before((2, 4)):
3950
return self._vfs_heads_to_fetch()
3952
return self._rpc_heads_to_fetch()
3953
except errors.UnknownSmartMethod:
3954
medium._remember_remote_is_before((2, 4))
3955
return self._vfs_heads_to_fetch()
3957
def _rpc_heads_to_fetch(self):
3958
response = self._call('Branch.heads_to_fetch', self._remote_path())
3959
if len(response) != 2:
3960
raise errors.UnexpectedSmartServerResponse(response)
3961
must_fetch, if_present_fetch = response
3962
return set(must_fetch), set(if_present_fetch)
3964
def _vfs_heads_to_fetch(self):
3966
return self._real_branch.heads_to_fetch()
3969
class RemoteConfig(object):
3970
"""A Config that reads and writes from smart verbs.
3972
It is a low-level object that considers config data to be name/value pairs
3973
that may be associated with a section. Assigning meaning to the these
3974
values is done at higher levels like bzrlib.config.TreeConfig.
3977
def get_option(self, name, section=None, default=None):
3978
"""Return the value associated with a named option.
3980
:param name: The name of the value
3981
:param section: The section the option is in (if any)
3982
:param default: The value to return if the value is not set
3983
:return: The value or default value
3986
configobj = self._get_configobj()
3989
section_obj = configobj
3992
section_obj = configobj[section]
3995
if section_obj is None:
3998
value = section_obj.get(name, default)
3999
except errors.UnknownSmartMethod:
4000
value = self._vfs_get_option(name, section, default)
4001
for hook in _mod_config.OldConfigHooks['get']:
4002
hook(self, name, value)
4005
def _response_to_configobj(self, response):
4006
if len(response[0]) and response[0][0] != 'ok':
4007
raise errors.UnexpectedSmartServerResponse(response)
4008
lines = response[1].read_body_bytes().splitlines()
4009
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4010
for hook in _mod_config.OldConfigHooks['load']:
4015
class RemoteBranchConfig(RemoteConfig):
4016
"""A RemoteConfig for Branches."""
4018
def __init__(self, branch):
4019
self._branch = branch
4021
def _get_configobj(self):
4022
path = self._branch._remote_path()
4023
response = self._branch._client.call_expecting_body(
4024
'Branch.get_config_file', path)
4025
return self._response_to_configobj(response)
4027
def set_option(self, value, name, section=None):
4028
"""Set the value associated with a named option.
4030
:param value: The value to set
4031
:param name: The name of the value to set
4032
:param section: The section the option is in (if any)
4034
medium = self._branch._client._medium
4035
if medium._is_remote_before((1, 14)):
4036
return self._vfs_set_option(value, name, section)
4037
if isinstance(value, dict):
4038
if medium._is_remote_before((2, 2)):
4039
return self._vfs_set_option(value, name, section)
4040
return self._set_config_option_dict(value, name, section)
4042
return self._set_config_option(value, name, section)
4044
def _set_config_option(self, value, name, section):
4046
path = self._branch._remote_path()
4047
response = self._branch._client.call('Branch.set_config_option',
4048
path, self._branch._lock_token, self._branch._repo_lock_token,
4049
value.encode('utf8'), name, section or '')
4050
except errors.UnknownSmartMethod:
4051
medium = self._branch._client._medium
4052
medium._remember_remote_is_before((1, 14))
4053
return self._vfs_set_option(value, name, section)
4055
raise errors.UnexpectedSmartServerResponse(response)
4057
def _serialize_option_dict(self, option_dict):
4059
for key, value in option_dict.items():
4060
if isinstance(key, unicode):
4061
key = key.encode('utf8')
4062
if isinstance(value, unicode):
4063
value = value.encode('utf8')
4064
utf8_dict[key] = value
4065
return bencode.bencode(utf8_dict)
4067
def _set_config_option_dict(self, value, name, section):
4069
path = self._branch._remote_path()
4070
serialised_dict = self._serialize_option_dict(value)
4071
response = self._branch._client.call(
4072
'Branch.set_config_option_dict',
4073
path, self._branch._lock_token, self._branch._repo_lock_token,
4074
serialised_dict, name, section or '')
4075
except errors.UnknownSmartMethod:
4076
medium = self._branch._client._medium
4077
medium._remember_remote_is_before((2, 2))
4078
return self._vfs_set_option(value, name, section)
4080
raise errors.UnexpectedSmartServerResponse(response)
4082
def _real_object(self):
4083
self._branch._ensure_real()
4084
return self._branch._real_branch
4086
def _vfs_set_option(self, value, name, section=None):
4087
return self._real_object()._get_config().set_option(
4088
value, name, section)
4091
class RemoteBzrDirConfig(RemoteConfig):
4092
"""A RemoteConfig for BzrDirs."""
4094
def __init__(self, bzrdir):
4095
self._bzrdir = bzrdir
4097
def _get_configobj(self):
4098
medium = self._bzrdir._client._medium
4099
verb = 'BzrDir.get_config_file'
4100
if medium._is_remote_before((1, 15)):
4101
raise errors.UnknownSmartMethod(verb)
4102
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4103
response = self._bzrdir._call_expecting_body(
4105
return self._response_to_configobj(response)
4107
def _vfs_get_option(self, name, section, default):
4108
return self._real_object()._get_config().get_option(
4109
name, section, default)
4111
def set_option(self, value, name, section=None):
4112
"""Set the value associated with a named option.
4114
:param value: The value to set
4115
:param name: The name of the value to set
4116
:param section: The section the option is in (if any)
4118
return self._real_object()._get_config().set_option(
4119
value, name, section)
4121
def _real_object(self):
4122
self._bzrdir._ensure_real()
4123
return self._bzrdir._real_bzrdir
4126
def _extract_tar(tar, to_dir):
4127
"""Extract all the contents of a tarfile object.
4129
A replacement for extractall, which is not present in python2.4
4132
tar.extract(tarinfo, to_dir)
4135
error_translators = registry.Registry()
4136
no_context_error_translators = registry.Registry()
4139
def _translate_error(err, **context):
4140
"""Translate an ErrorFromSmartServer into a more useful error.
4142
Possible context keys:
4150
If the error from the server doesn't match a known pattern, then
4151
UnknownErrorFromSmartServer is raised.
4155
return context[name]
4156
except KeyError, key_err:
4157
mutter('Missing key %r in context %r', key_err.args[0], context)
4160
"""Get the path from the context if present, otherwise use first error
4164
return context['path']
4165
except KeyError, key_err:
4167
return err.error_args[0]
4168
except IndexError, idx_err:
4170
'Missing key %r in context %r', key_err.args[0], context)
4174
translator = error_translators.get(err.error_verb)
4178
raise translator(err, find, get_path)
4180
translator = no_context_error_translators.get(err.error_verb)
4182
raise errors.UnknownErrorFromSmartServer(err)
4184
raise translator(err)
4187
error_translators.register('NoSuchRevision',
4188
lambda err, find, get_path: NoSuchRevision(
4189
find('branch'), err.error_args[0]))
4190
error_translators.register('nosuchrevision',
4191
lambda err, find, get_path: NoSuchRevision(
4192
find('repository'), err.error_args[0]))
4194
def _translate_nobranch_error(err, find, get_path):
4195
if len(err.error_args) >= 1:
4196
extra = err.error_args[0]
4199
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4202
error_translators.register('nobranch', _translate_nobranch_error)
4203
error_translators.register('norepository',
4204
lambda err, find, get_path: errors.NoRepositoryPresent(
4206
error_translators.register('UnlockableTransport',
4207
lambda err, find, get_path: errors.UnlockableTransport(
4208
find('bzrdir').root_transport))
4209
error_translators.register('TokenMismatch',
4210
lambda err, find, get_path: errors.TokenMismatch(
4211
find('token'), '(remote token)'))
4212
error_translators.register('Diverged',
4213
lambda err, find, get_path: errors.DivergedBranches(
4214
find('branch'), find('other_branch')))
4215
error_translators.register('NotStacked',
4216
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4218
def _translate_PermissionDenied(err, find, get_path):
4220
if len(err.error_args) >= 2:
4221
extra = err.error_args[1]
4224
return errors.PermissionDenied(path, extra=extra)
4226
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4227
error_translators.register('ReadError',
4228
lambda err, find, get_path: errors.ReadError(get_path()))
4229
error_translators.register('NoSuchFile',
4230
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4231
error_translators.register('TokenLockingNotSupported',
4232
lambda err, find, get_path: errors.TokenLockingNotSupported(
4233
find('repository')))
4234
error_translators.register('UnsuspendableWriteGroup',
4235
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4236
repository=find('repository')))
4237
error_translators.register('UnresumableWriteGroup',
4238
lambda err, find, get_path: errors.UnresumableWriteGroup(
4239
repository=find('repository'), write_groups=err.error_args[0],
4240
reason=err.error_args[1]))
4241
no_context_error_translators.register('IncompatibleRepositories',
4242
lambda err: errors.IncompatibleRepositories(
4243
err.error_args[0], err.error_args[1], err.error_args[2]))
4244
no_context_error_translators.register('LockContention',
4245
lambda err: errors.LockContention('(remote lock)'))
4246
no_context_error_translators.register('LockFailed',
4247
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4248
no_context_error_translators.register('TipChangeRejected',
4249
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4250
no_context_error_translators.register('UnstackableBranchFormat',
4251
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4252
no_context_error_translators.register('UnstackableRepositoryFormat',
4253
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4254
no_context_error_translators.register('FileExists',
4255
lambda err: errors.FileExists(err.error_args[0]))
4256
no_context_error_translators.register('DirectoryNotEmpty',
4257
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4259
def _translate_short_readv_error(err):
4260
args = err.error_args
4261
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4264
no_context_error_translators.register('ShortReadvError',
4265
_translate_short_readv_error)
4267
def _translate_unicode_error(err):
4268
encoding = str(err.error_args[0]) # encoding must always be a string
4269
val = err.error_args[1]
4270
start = int(err.error_args[2])
4271
end = int(err.error_args[3])
4272
reason = str(err.error_args[4]) # reason must always be a string
4273
if val.startswith('u:'):
4274
val = val[2:].decode('utf-8')
4275
elif val.startswith('s:'):
4276
val = val[2:].decode('base64')
4277
if err.error_verb == 'UnicodeDecodeError':
4278
raise UnicodeDecodeError(encoding, val, start, end, reason)
4279
elif err.error_verb == 'UnicodeEncodeError':
4280
raise UnicodeEncodeError(encoding, val, start, end, reason)
4282
no_context_error_translators.register('UnicodeEncodeError',
4283
_translate_unicode_error)
4284
no_context_error_translators.register('UnicodeDecodeError',
4285
_translate_unicode_error)
4286
no_context_error_translators.register('ReadOnlyError',
4287
lambda err: errors.TransportNotPossible('readonly transport'))
4288
no_context_error_translators.register('MemoryError',
4289
lambda err: errors.BzrError("remote server out of memory\n"
4290
"Retry non-remotely, or contact the server admin for details."))
4291
no_context_error_translators.register('RevisionNotPresent',
4292
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4294
no_context_error_translators.register('BzrCheckError',
4295
lambda err: errors.BzrCheckError(msg=err.error_args[0]))