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):
628
# as per meta1 formats - just delegate to the format object which may
630
real_branch = self._format.get_branch_format().initialize(self,
631
name=name, repository=repository,
632
append_revisions_only=append_revisions_only)
633
if not isinstance(real_branch, RemoteBranch):
634
if not isinstance(repository, RemoteRepository):
635
raise AssertionError(
636
'need a RemoteRepository to use with RemoteBranch, got %r'
638
result = RemoteBranch(self, repository, real_branch, name=name)
641
# BzrDir.clone_on_transport() uses the result of create_branch but does
642
# not return it to its callers; we save approximately 8% of our round
643
# trips by handing the branch we created back to the first caller to
644
# open_branch rather than probing anew. Long term we need a API in
645
# bzrdir that doesn't discard result objects (like result_branch).
647
self._next_open_branch_result = result
650
def destroy_branch(self, name=None):
651
"""See BzrDir.destroy_branch"""
652
path = self._path_for_remote_call(self._client)
658
response = self._call('BzrDir.destroy_branch', path, *args)
659
except errors.UnknownSmartMethod:
661
self._real_bzrdir.destroy_branch(name=name)
662
self._next_open_branch_result = None
664
self._next_open_branch_result = None
665
if response[0] != 'ok':
666
raise SmartProtocolError('unexpected response code %s' % (response,))
668
def create_workingtree(self, revision_id=None, from_branch=None,
669
accelerator_tree=None, hardlink=False):
670
raise errors.NotLocalUrl(self.transport.base)
672
def find_branch_format(self, name=None):
673
"""Find the branch 'format' for this bzrdir.
675
This might be a synthetic object for e.g. RemoteBranch and SVN.
677
b = self.open_branch(name=name)
680
def get_branch_reference(self, name=None):
681
"""See BzrDir.get_branch_reference()."""
683
# XXX JRV20100304: Support opening colocated branches
684
raise errors.NoColocatedBranchSupport(self)
685
response = self._get_branch_reference()
686
if response[0] == 'ref':
691
def _get_branch_reference(self):
692
path = self._path_for_remote_call(self._client)
693
medium = self._client._medium
695
('BzrDir.open_branchV3', (2, 1)),
696
('BzrDir.open_branchV2', (1, 13)),
697
('BzrDir.open_branch', None),
699
for verb, required_version in candidate_calls:
700
if required_version and medium._is_remote_before(required_version):
703
response = self._call(verb, path)
704
except errors.UnknownSmartMethod:
705
if required_version is None:
707
medium._remember_remote_is_before(required_version)
710
if verb == 'BzrDir.open_branch':
711
if response[0] != 'ok':
712
raise errors.UnexpectedSmartServerResponse(response)
713
if response[1] != '':
714
return ('ref', response[1])
716
return ('branch', '')
717
if response[0] not in ('ref', 'branch'):
718
raise errors.UnexpectedSmartServerResponse(response)
721
def _get_tree_branch(self, name=None):
722
"""See BzrDir._get_tree_branch()."""
723
return None, self.open_branch(name=name)
725
def open_branch(self, name=None, unsupported=False,
726
ignore_fallbacks=False, possible_transports=None):
728
raise NotImplementedError('unsupported flag support not implemented yet.')
729
if self._next_open_branch_result is not None:
730
# See create_branch for details.
731
result = self._next_open_branch_result
732
self._next_open_branch_result = None
734
response = self._get_branch_reference()
735
if response[0] == 'ref':
736
# a branch reference, use the existing BranchReference logic.
737
format = BranchReferenceFormat()
738
return format.open(self, name=name, _found=True,
739
location=response[1], ignore_fallbacks=ignore_fallbacks,
740
possible_transports=possible_transports)
741
branch_format_name = response[1]
742
if not branch_format_name:
743
branch_format_name = None
744
format = RemoteBranchFormat(network_name=branch_format_name)
745
return RemoteBranch(self, self.find_repository(), format=format,
746
setup_stacking=not ignore_fallbacks, name=name,
747
possible_transports=possible_transports)
749
def _open_repo_v1(self, path):
750
verb = 'BzrDir.find_repository'
751
response = self._call(verb, path)
752
if response[0] != 'ok':
753
raise errors.UnexpectedSmartServerResponse(response)
754
# servers that only support the v1 method don't support external
757
repo = self._real_bzrdir.open_repository()
758
response = response + ('no', repo._format.network_name())
759
return response, repo
761
def _open_repo_v2(self, path):
762
verb = 'BzrDir.find_repositoryV2'
763
response = self._call(verb, path)
764
if response[0] != 'ok':
765
raise errors.UnexpectedSmartServerResponse(response)
767
repo = self._real_bzrdir.open_repository()
768
response = response + (repo._format.network_name(),)
769
return response, repo
771
def _open_repo_v3(self, path):
772
verb = 'BzrDir.find_repositoryV3'
773
medium = self._client._medium
774
if medium._is_remote_before((1, 13)):
775
raise errors.UnknownSmartMethod(verb)
777
response = self._call(verb, path)
778
except errors.UnknownSmartMethod:
779
medium._remember_remote_is_before((1, 13))
781
if response[0] != 'ok':
782
raise errors.UnexpectedSmartServerResponse(response)
783
return response, None
785
def open_repository(self):
786
path = self._path_for_remote_call(self._client)
788
for probe in [self._open_repo_v3, self._open_repo_v2,
791
response, real_repo = probe(path)
793
except errors.UnknownSmartMethod:
796
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
797
if response[0] != 'ok':
798
raise errors.UnexpectedSmartServerResponse(response)
799
if len(response) != 6:
800
raise SmartProtocolError('incorrect response length %s' % (response,))
801
if response[1] == '':
802
# repo is at this dir.
803
format = response_tuple_to_repo_format(response[2:])
804
# Used to support creating a real format instance when needed.
805
format._creating_bzrdir = self
806
remote_repo = RemoteRepository(self, format)
807
format._creating_repo = remote_repo
808
if real_repo is not None:
809
remote_repo._set_real_repository(real_repo)
812
raise errors.NoRepositoryPresent(self)
814
def has_workingtree(self):
815
if self._has_working_tree is None:
816
path = self._path_for_remote_call(self._client)
818
response = self._call('BzrDir.has_workingtree', path)
819
except errors.UnknownSmartMethod:
821
self._has_working_tree = self._real_bzrdir.has_workingtree()
823
if response[0] not in ('yes', 'no'):
824
raise SmartProtocolError('unexpected response code %s' % (response,))
825
self._has_working_tree = (response[0] == 'yes')
826
return self._has_working_tree
828
def open_workingtree(self, recommend_upgrade=True):
829
if self.has_workingtree():
830
raise errors.NotLocalUrl(self.root_transport)
832
raise errors.NoWorkingTree(self.root_transport.base)
834
def _path_for_remote_call(self, client):
835
"""Return the path to be used for this bzrdir in a remote call."""
836
return urlutils.split_segment_parameters_raw(
837
client.remote_path_from_transport(self.root_transport))[0]
839
def get_branch_transport(self, branch_format, name=None):
841
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
843
def get_repository_transport(self, repository_format):
845
return self._real_bzrdir.get_repository_transport(repository_format)
847
def get_workingtree_transport(self, workingtree_format):
849
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
851
def can_convert_format(self):
852
"""Upgrading of remote bzrdirs is not supported yet."""
855
def needs_format_conversion(self, format):
856
"""Upgrading of remote bzrdirs is not supported yet."""
859
def _get_config(self):
860
return RemoteBzrDirConfig(self)
862
def _get_config_store(self):
863
return RemoteControlStore(self)
866
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
867
"""Format for repositories accessed over a _SmartClient.
869
Instances of this repository are represented by RemoteRepository
872
The RemoteRepositoryFormat is parameterized during construction
873
to reflect the capabilities of the real, remote format. Specifically
874
the attributes rich_root_data and supports_tree_reference are set
875
on a per instance basis, and are not set (and should not be) at
878
:ivar _custom_format: If set, a specific concrete repository format that
879
will be used when initializing a repository with this
880
RemoteRepositoryFormat.
881
:ivar _creating_repo: If set, the repository object that this
882
RemoteRepositoryFormat was created for: it can be called into
883
to obtain data like the network name.
886
_matchingbzrdir = RemoteBzrDirFormat()
887
supports_full_versioned_files = True
888
supports_leaving_lock = True
891
_mod_repository.RepositoryFormat.__init__(self)
892
self._custom_format = None
893
self._network_name = None
894
self._creating_bzrdir = None
895
self._revision_graph_can_have_wrong_parents = None
896
self._supports_chks = None
897
self._supports_external_lookups = None
898
self._supports_tree_reference = None
899
self._supports_funky_characters = None
900
self._supports_nesting_repositories = None
901
self._rich_root_data = None
904
return "%s(_network_name=%r)" % (self.__class__.__name__,
908
def fast_deltas(self):
910
return self._custom_format.fast_deltas
913
def rich_root_data(self):
914
if self._rich_root_data is None:
916
self._rich_root_data = self._custom_format.rich_root_data
917
return self._rich_root_data
920
def supports_chks(self):
921
if self._supports_chks is None:
923
self._supports_chks = self._custom_format.supports_chks
924
return self._supports_chks
927
def supports_external_lookups(self):
928
if self._supports_external_lookups is None:
930
self._supports_external_lookups = \
931
self._custom_format.supports_external_lookups
932
return self._supports_external_lookups
935
def supports_funky_characters(self):
936
if self._supports_funky_characters is None:
938
self._supports_funky_characters = \
939
self._custom_format.supports_funky_characters
940
return self._supports_funky_characters
943
def supports_nesting_repositories(self):
944
if self._supports_nesting_repositories is None:
946
self._supports_nesting_repositories = \
947
self._custom_format.supports_nesting_repositories
948
return self._supports_nesting_repositories
951
def supports_tree_reference(self):
952
if self._supports_tree_reference is None:
954
self._supports_tree_reference = \
955
self._custom_format.supports_tree_reference
956
return self._supports_tree_reference
959
def revision_graph_can_have_wrong_parents(self):
960
if self._revision_graph_can_have_wrong_parents is None:
962
self._revision_graph_can_have_wrong_parents = \
963
self._custom_format.revision_graph_can_have_wrong_parents
964
return self._revision_graph_can_have_wrong_parents
966
def _vfs_initialize(self, a_bzrdir, shared):
967
"""Helper for common code in initialize."""
968
if self._custom_format:
969
# Custom format requested
970
result = self._custom_format.initialize(a_bzrdir, shared=shared)
971
elif self._creating_bzrdir is not None:
972
# Use the format that the repository we were created to back
974
prior_repo = self._creating_bzrdir.open_repository()
975
prior_repo._ensure_real()
976
result = prior_repo._real_repository._format.initialize(
977
a_bzrdir, shared=shared)
979
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
980
# support remote initialization.
981
# We delegate to a real object at this point (as RemoteBzrDir
982
# delegate to the repository format which would lead to infinite
983
# recursion if we just called a_bzrdir.create_repository.
984
a_bzrdir._ensure_real()
985
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
986
if not isinstance(result, RemoteRepository):
987
return self.open(a_bzrdir)
991
def initialize(self, a_bzrdir, shared=False):
992
# Being asked to create on a non RemoteBzrDir:
993
if not isinstance(a_bzrdir, RemoteBzrDir):
994
return self._vfs_initialize(a_bzrdir, shared)
995
medium = a_bzrdir._client._medium
996
if medium._is_remote_before((1, 13)):
997
return self._vfs_initialize(a_bzrdir, shared)
998
# Creating on a remote bzr dir.
999
# 1) get the network name to use.
1000
if self._custom_format:
1001
network_name = self._custom_format.network_name()
1002
elif self._network_name:
1003
network_name = self._network_name
1005
# Select the current bzrlib default and ask for that.
1006
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1007
reference_format = reference_bzrdir_format.repository_format
1008
network_name = reference_format.network_name()
1009
# 2) try direct creation via RPC
1010
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1011
verb = 'BzrDir.create_repository'
1015
shared_str = 'False'
1017
response = a_bzrdir._call(verb, path, network_name, shared_str)
1018
except errors.UnknownSmartMethod:
1019
# Fallback - use vfs methods
1020
medium._remember_remote_is_before((1, 13))
1021
return self._vfs_initialize(a_bzrdir, shared)
1023
# Turn the response into a RemoteRepository object.
1024
format = response_tuple_to_repo_format(response[1:])
1025
# Used to support creating a real format instance when needed.
1026
format._creating_bzrdir = a_bzrdir
1027
remote_repo = RemoteRepository(a_bzrdir, format)
1028
format._creating_repo = remote_repo
1031
def open(self, a_bzrdir):
1032
if not isinstance(a_bzrdir, RemoteBzrDir):
1033
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1034
return a_bzrdir.open_repository()
1036
def _ensure_real(self):
1037
if self._custom_format is None:
1039
self._custom_format = _mod_repository.network_format_registry.get(
1042
raise errors.UnknownFormatError(kind='repository',
1043
format=self._network_name)
1046
def _fetch_order(self):
1048
return self._custom_format._fetch_order
1051
def _fetch_uses_deltas(self):
1053
return self._custom_format._fetch_uses_deltas
1056
def _fetch_reconcile(self):
1058
return self._custom_format._fetch_reconcile
1060
def get_format_description(self):
1062
return 'Remote: ' + self._custom_format.get_format_description()
1064
def __eq__(self, other):
1065
return self.__class__ is other.__class__
1067
def network_name(self):
1068
if self._network_name:
1069
return self._network_name
1070
self._creating_repo._ensure_real()
1071
return self._creating_repo._real_repository._format.network_name()
1074
def pack_compresses(self):
1076
return self._custom_format.pack_compresses
1079
def _serializer(self):
1081
return self._custom_format._serializer
1084
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1085
lock._RelockDebugMixin):
1086
"""Repository accessed over rpc.
1088
For the moment most operations are performed using local transport-backed
1092
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1093
"""Create a RemoteRepository instance.
1095
:param remote_bzrdir: The bzrdir hosting this repository.
1096
:param format: The RemoteFormat object to use.
1097
:param real_repository: If not None, a local implementation of the
1098
repository logic for the repository, usually accessing the data
1100
:param _client: Private testing parameter - override the smart client
1101
to be used by the repository.
1104
self._real_repository = real_repository
1106
self._real_repository = None
1107
self.bzrdir = remote_bzrdir
1109
self._client = remote_bzrdir._client
1111
self._client = _client
1112
self._format = format
1113
self._lock_mode = None
1114
self._lock_token = None
1115
self._write_group_tokens = None
1116
self._lock_count = 0
1117
self._leave_lock = False
1118
# Cache of revision parents; misses are cached during read locks, and
1119
# write locks when no _real_repository has been set.
1120
self._unstacked_provider = graph.CachingParentsProvider(
1121
get_parent_map=self._get_parent_map_rpc)
1122
self._unstacked_provider.disable_cache()
1124
# These depend on the actual remote format, so force them off for
1125
# maximum compatibility. XXX: In future these should depend on the
1126
# remote repository instance, but this is irrelevant until we perform
1127
# reconcile via an RPC call.
1128
self._reconcile_does_inventory_gc = False
1129
self._reconcile_fixes_text_parents = False
1130
self._reconcile_backsup_inventory = False
1131
self.base = self.bzrdir.transport.base
1132
# Additional places to query for data.
1133
self._fallback_repositories = []
1136
def user_transport(self):
1137
return self.bzrdir.user_transport
1140
def control_transport(self):
1141
# XXX: Normally you shouldn't directly get at the remote repository
1142
# transport, but I'm not sure it's worth making this method
1143
# optional -- mbp 2010-04-21
1144
return self.bzrdir.get_repository_transport(None)
1147
return "%s(%s)" % (self.__class__.__name__, self.base)
1151
def abort_write_group(self, suppress_errors=False):
1152
"""Complete a write group on the decorated repository.
1154
Smart methods perform operations in a single step so this API
1155
is not really applicable except as a compatibility thunk
1156
for older plugins that don't use e.g. the CommitBuilder
1159
:param suppress_errors: see Repository.abort_write_group.
1161
if self._real_repository:
1163
return self._real_repository.abort_write_group(
1164
suppress_errors=suppress_errors)
1165
if not self.is_in_write_group():
1167
mutter('(suppressed) not in write group')
1169
raise errors.BzrError("not in write group")
1170
path = self.bzrdir._path_for_remote_call(self._client)
1172
response = self._call('Repository.abort_write_group', path,
1173
self._lock_token, self._write_group_tokens)
1174
except Exception, exc:
1175
self._write_group = None
1176
if not suppress_errors:
1178
mutter('abort_write_group failed')
1179
log_exception_quietly()
1180
note(gettext('bzr: ERROR (ignored): %s'), exc)
1182
if response != ('ok', ):
1183
raise errors.UnexpectedSmartServerResponse(response)
1184
self._write_group_tokens = None
1187
def chk_bytes(self):
1188
"""Decorate the real repository for now.
1190
In the long term a full blown network facility is needed to avoid
1191
creating a real repository object locally.
1194
return self._real_repository.chk_bytes
1196
def commit_write_group(self):
1197
"""Complete a write group on the decorated repository.
1199
Smart methods perform operations in a single step so this API
1200
is not really applicable except as a compatibility thunk
1201
for older plugins that don't use e.g. the CommitBuilder
1204
if self._real_repository:
1206
return self._real_repository.commit_write_group()
1207
if not self.is_in_write_group():
1208
raise errors.BzrError("not in write group")
1209
path = self.bzrdir._path_for_remote_call(self._client)
1210
response = self._call('Repository.commit_write_group', path,
1211
self._lock_token, self._write_group_tokens)
1212
if response != ('ok', ):
1213
raise errors.UnexpectedSmartServerResponse(response)
1214
self._write_group_tokens = None
1215
# Refresh data after writing to the repository.
1218
def resume_write_group(self, tokens):
1219
if self._real_repository:
1220
return self._real_repository.resume_write_group(tokens)
1221
path = self.bzrdir._path_for_remote_call(self._client)
1223
response = self._call('Repository.check_write_group', path,
1224
self._lock_token, tokens)
1225
except errors.UnknownSmartMethod:
1227
return self._real_repository.resume_write_group(tokens)
1228
if response != ('ok', ):
1229
raise errors.UnexpectedSmartServerResponse(response)
1230
self._write_group_tokens = tokens
1232
def suspend_write_group(self):
1233
if self._real_repository:
1234
return self._real_repository.suspend_write_group()
1235
ret = self._write_group_tokens or []
1236
self._write_group_tokens = None
1239
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1241
return self._real_repository.get_missing_parent_inventories(
1242
check_for_missing_texts=check_for_missing_texts)
1244
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1246
return self._real_repository.get_rev_id_for_revno(
1249
def get_rev_id_for_revno(self, revno, known_pair):
1250
"""See Repository.get_rev_id_for_revno."""
1251
path = self.bzrdir._path_for_remote_call(self._client)
1253
if self._client._medium._is_remote_before((1, 17)):
1254
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1255
response = self._call(
1256
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1257
except errors.UnknownSmartMethod:
1258
self._client._medium._remember_remote_is_before((1, 17))
1259
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1260
if response[0] == 'ok':
1261
return True, response[1]
1262
elif response[0] == 'history-incomplete':
1263
known_pair = response[1:3]
1264
for fallback in self._fallback_repositories:
1265
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1270
# Not found in any fallbacks
1271
return False, known_pair
1273
raise errors.UnexpectedSmartServerResponse(response)
1275
def _ensure_real(self):
1276
"""Ensure that there is a _real_repository set.
1278
Used before calls to self._real_repository.
1280
Note that _ensure_real causes many roundtrips to the server which are
1281
not desirable, and prevents the use of smart one-roundtrip RPC's to
1282
perform complex operations (such as accessing parent data, streaming
1283
revisions etc). Adding calls to _ensure_real should only be done when
1284
bringing up new functionality, adding fallbacks for smart methods that
1285
require a fallback path, and never to replace an existing smart method
1286
invocation. If in doubt chat to the bzr network team.
1288
if self._real_repository is None:
1289
if 'hpssvfs' in debug.debug_flags:
1291
warning('VFS Repository access triggered\n%s',
1292
''.join(traceback.format_stack()))
1293
self._unstacked_provider.missing_keys.clear()
1294
self.bzrdir._ensure_real()
1295
self._set_real_repository(
1296
self.bzrdir._real_bzrdir.open_repository())
1298
def _translate_error(self, err, **context):
1299
self.bzrdir._translate_error(err, repository=self, **context)
1301
def find_text_key_references(self):
1302
"""Find the text key references within the repository.
1304
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1305
to whether they were referred to by the inventory of the
1306
revision_id that they contain. The inventory texts from all present
1307
revision ids are assessed to generate this report.
1310
return self._real_repository.find_text_key_references()
1312
def _generate_text_key_index(self):
1313
"""Generate a new text key index for the repository.
1315
This is an expensive function that will take considerable time to run.
1317
:return: A dict mapping (file_id, revision_id) tuples to a list of
1318
parents, also (file_id, revision_id) tuples.
1321
return self._real_repository._generate_text_key_index()
1323
def _get_revision_graph(self, revision_id):
1324
"""Private method for using with old (< 1.2) servers to fallback."""
1325
if revision_id is None:
1327
elif _mod_revision.is_null(revision_id):
1330
path = self.bzrdir._path_for_remote_call(self._client)
1331
response = self._call_expecting_body(
1332
'Repository.get_revision_graph', path, revision_id)
1333
response_tuple, response_handler = response
1334
if response_tuple[0] != 'ok':
1335
raise errors.UnexpectedSmartServerResponse(response_tuple)
1336
coded = response_handler.read_body_bytes()
1338
# no revisions in this repository!
1340
lines = coded.split('\n')
1343
d = tuple(line.split())
1344
revision_graph[d[0]] = d[1:]
1346
return revision_graph
1348
def _get_sink(self):
1349
"""See Repository._get_sink()."""
1350
return RemoteStreamSink(self)
1352
def _get_source(self, to_format):
1353
"""Return a source for streaming from this repository."""
1354
return RemoteStreamSource(self, to_format)
1357
def get_file_graph(self):
1358
return graph.Graph(self.texts)
1361
def has_revision(self, revision_id):
1362
"""True if this repository has a copy of the revision."""
1363
# Copy of bzrlib.repository.Repository.has_revision
1364
return revision_id in self.has_revisions((revision_id,))
1367
def has_revisions(self, revision_ids):
1368
"""Probe to find out the presence of multiple revisions.
1370
:param revision_ids: An iterable of revision_ids.
1371
:return: A set of the revision_ids that were present.
1373
# Copy of bzrlib.repository.Repository.has_revisions
1374
parent_map = self.get_parent_map(revision_ids)
1375
result = set(parent_map)
1376
if _mod_revision.NULL_REVISION in revision_ids:
1377
result.add(_mod_revision.NULL_REVISION)
1380
def _has_same_fallbacks(self, other_repo):
1381
"""Returns true if the repositories have the same fallbacks."""
1382
# XXX: copied from Repository; it should be unified into a base class
1383
# <https://bugs.launchpad.net/bzr/+bug/401622>
1384
my_fb = self._fallback_repositories
1385
other_fb = other_repo._fallback_repositories
1386
if len(my_fb) != len(other_fb):
1388
for f, g in zip(my_fb, other_fb):
1389
if not f.has_same_location(g):
1393
def has_same_location(self, other):
1394
# TODO: Move to RepositoryBase and unify with the regular Repository
1395
# one; unfortunately the tests rely on slightly different behaviour at
1396
# present -- mbp 20090710
1397
return (self.__class__ is other.__class__ and
1398
self.bzrdir.transport.base == other.bzrdir.transport.base)
1400
def get_graph(self, other_repository=None):
1401
"""Return the graph for this repository format"""
1402
parents_provider = self._make_parents_provider(other_repository)
1403
return graph.Graph(parents_provider)
1406
def get_known_graph_ancestry(self, revision_ids):
1407
"""Return the known graph for a set of revision ids and their ancestors.
1409
st = static_tuple.StaticTuple
1410
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1411
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1412
return graph.GraphThunkIdsToKeys(known_graph)
1414
def gather_stats(self, revid=None, committers=None):
1415
"""See Repository.gather_stats()."""
1416
path = self.bzrdir._path_for_remote_call(self._client)
1417
# revid can be None to indicate no revisions, not just NULL_REVISION
1418
if revid is None or _mod_revision.is_null(revid):
1422
if committers is None or not committers:
1423
fmt_committers = 'no'
1425
fmt_committers = 'yes'
1426
response_tuple, response_handler = self._call_expecting_body(
1427
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1428
if response_tuple[0] != 'ok':
1429
raise errors.UnexpectedSmartServerResponse(response_tuple)
1431
body = response_handler.read_body_bytes()
1433
for line in body.split('\n'):
1436
key, val_text = line.split(':')
1437
if key in ('revisions', 'size', 'committers'):
1438
result[key] = int(val_text)
1439
elif key in ('firstrev', 'latestrev'):
1440
values = val_text.split(' ')[1:]
1441
result[key] = (float(values[0]), long(values[1]))
1445
def find_branches(self, using=False):
1446
"""See Repository.find_branches()."""
1447
# should be an API call to the server.
1449
return self._real_repository.find_branches(using=using)
1451
def get_physical_lock_status(self):
1452
"""See Repository.get_physical_lock_status()."""
1453
path = self.bzrdir._path_for_remote_call(self._client)
1455
response = self._call('Repository.get_physical_lock_status', path)
1456
except errors.UnknownSmartMethod:
1458
return self._real_repository.get_physical_lock_status()
1459
if response[0] not in ('yes', 'no'):
1460
raise errors.UnexpectedSmartServerResponse(response)
1461
return (response[0] == 'yes')
1463
def is_in_write_group(self):
1464
"""Return True if there is an open write group.
1466
write groups are only applicable locally for the smart server..
1468
if self._write_group_tokens is not None:
1470
if self._real_repository:
1471
return self._real_repository.is_in_write_group()
1473
def is_locked(self):
1474
return self._lock_count >= 1
1476
def is_shared(self):
1477
"""See Repository.is_shared()."""
1478
path = self.bzrdir._path_for_remote_call(self._client)
1479
response = self._call('Repository.is_shared', path)
1480
if response[0] not in ('yes', 'no'):
1481
raise SmartProtocolError('unexpected response code %s' % (response,))
1482
return response[0] == 'yes'
1484
def is_write_locked(self):
1485
return self._lock_mode == 'w'
1487
def _warn_if_deprecated(self, branch=None):
1488
# If we have a real repository, the check will be done there, if we
1489
# don't the check will be done remotely.
1492
def lock_read(self):
1493
"""Lock the repository for read operations.
1495
:return: A bzrlib.lock.LogicalLockResult.
1497
# wrong eventually - want a local lock cache context
1498
if not self._lock_mode:
1499
self._note_lock('r')
1500
self._lock_mode = 'r'
1501
self._lock_count = 1
1502
self._unstacked_provider.enable_cache(cache_misses=True)
1503
if self._real_repository is not None:
1504
self._real_repository.lock_read()
1505
for repo in self._fallback_repositories:
1508
self._lock_count += 1
1509
return lock.LogicalLockResult(self.unlock)
1511
def _remote_lock_write(self, token):
1512
path = self.bzrdir._path_for_remote_call(self._client)
1515
err_context = {'token': token}
1516
response = self._call('Repository.lock_write', path, token,
1518
if response[0] == 'ok':
1519
ok, token = response
1522
raise errors.UnexpectedSmartServerResponse(response)
1524
def lock_write(self, token=None, _skip_rpc=False):
1525
if not self._lock_mode:
1526
self._note_lock('w')
1528
if self._lock_token is not None:
1529
if token != self._lock_token:
1530
raise errors.TokenMismatch(token, self._lock_token)
1531
self._lock_token = token
1533
self._lock_token = self._remote_lock_write(token)
1534
# if self._lock_token is None, then this is something like packs or
1535
# svn where we don't get to lock the repo, or a weave style repository
1536
# where we cannot lock it over the wire and attempts to do so will
1538
if self._real_repository is not None:
1539
self._real_repository.lock_write(token=self._lock_token)
1540
if token is not None:
1541
self._leave_lock = True
1543
self._leave_lock = False
1544
self._lock_mode = 'w'
1545
self._lock_count = 1
1546
cache_misses = self._real_repository is None
1547
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1548
for repo in self._fallback_repositories:
1549
# Writes don't affect fallback repos
1551
elif self._lock_mode == 'r':
1552
raise errors.ReadOnlyError(self)
1554
self._lock_count += 1
1555
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1557
def leave_lock_in_place(self):
1558
if not self._lock_token:
1559
raise NotImplementedError(self.leave_lock_in_place)
1560
self._leave_lock = True
1562
def dont_leave_lock_in_place(self):
1563
if not self._lock_token:
1564
raise NotImplementedError(self.dont_leave_lock_in_place)
1565
self._leave_lock = False
1567
def _set_real_repository(self, repository):
1568
"""Set the _real_repository for this repository.
1570
:param repository: The repository to fallback to for non-hpss
1571
implemented operations.
1573
if self._real_repository is not None:
1574
# Replacing an already set real repository.
1575
# We cannot do this [currently] if the repository is locked -
1576
# synchronised state might be lost.
1577
if self.is_locked():
1578
raise AssertionError('_real_repository is already set')
1579
if isinstance(repository, RemoteRepository):
1580
raise AssertionError()
1581
self._real_repository = repository
1582
# three code paths happen here:
1583
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1584
# up stacking. In this case self._fallback_repositories is [], and the
1585
# real repo is already setup. Preserve the real repo and
1586
# RemoteRepository.add_fallback_repository will avoid adding
1588
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1589
# ensure_real is triggered from a branch, the real repository to
1590
# set already has a matching list with separate instances, but
1591
# as they are also RemoteRepositories we don't worry about making the
1592
# lists be identical.
1593
# 3) new servers, RemoteRepository.ensure_real is triggered before
1594
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1595
# and need to populate it.
1596
if (self._fallback_repositories and
1597
len(self._real_repository._fallback_repositories) !=
1598
len(self._fallback_repositories)):
1599
if len(self._real_repository._fallback_repositories):
1600
raise AssertionError(
1601
"cannot cleanly remove existing _fallback_repositories")
1602
for fb in self._fallback_repositories:
1603
self._real_repository.add_fallback_repository(fb)
1604
if self._lock_mode == 'w':
1605
# if we are already locked, the real repository must be able to
1606
# acquire the lock with our token.
1607
self._real_repository.lock_write(self._lock_token)
1608
elif self._lock_mode == 'r':
1609
self._real_repository.lock_read()
1610
if self._write_group_tokens is not None:
1611
# if we are already in a write group, resume it
1612
self._real_repository.resume_write_group(self._write_group_tokens)
1613
self._write_group_tokens = None
1615
def start_write_group(self):
1616
"""Start a write group on the decorated repository.
1618
Smart methods perform operations in a single step so this API
1619
is not really applicable except as a compatibility thunk
1620
for older plugins that don't use e.g. the CommitBuilder
1623
if self._real_repository:
1625
return self._real_repository.start_write_group()
1626
if not self.is_write_locked():
1627
raise errors.NotWriteLocked(self)
1628
if self._write_group_tokens is not None:
1629
raise errors.BzrError('already in a write group')
1630
path = self.bzrdir._path_for_remote_call(self._client)
1632
response = self._call('Repository.start_write_group', path,
1634
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1636
return self._real_repository.start_write_group()
1637
if response[0] != 'ok':
1638
raise errors.UnexpectedSmartServerResponse(response)
1639
self._write_group_tokens = response[1]
1641
def _unlock(self, token):
1642
path = self.bzrdir._path_for_remote_call(self._client)
1644
# with no token the remote repository is not persistently locked.
1646
err_context = {'token': token}
1647
response = self._call('Repository.unlock', path, token,
1649
if response == ('ok',):
1652
raise errors.UnexpectedSmartServerResponse(response)
1654
@only_raises(errors.LockNotHeld, errors.LockBroken)
1656
if not self._lock_count:
1657
return lock.cant_unlock_not_held(self)
1658
self._lock_count -= 1
1659
if self._lock_count > 0:
1661
self._unstacked_provider.disable_cache()
1662
old_mode = self._lock_mode
1663
self._lock_mode = None
1665
# The real repository is responsible at present for raising an
1666
# exception if it's in an unfinished write group. However, it
1667
# normally will *not* actually remove the lock from disk - that's
1668
# done by the server on receiving the Repository.unlock call.
1669
# This is just to let the _real_repository stay up to date.
1670
if self._real_repository is not None:
1671
self._real_repository.unlock()
1672
elif self._write_group_tokens is not None:
1673
self.abort_write_group()
1675
# The rpc-level lock should be released even if there was a
1676
# problem releasing the vfs-based lock.
1678
# Only write-locked repositories need to make a remote method
1679
# call to perform the unlock.
1680
old_token = self._lock_token
1681
self._lock_token = None
1682
if not self._leave_lock:
1683
self._unlock(old_token)
1684
# Fallbacks are always 'lock_read()' so we don't pay attention to
1686
for repo in self._fallback_repositories:
1689
def break_lock(self):
1690
# should hand off to the network
1691
path = self.bzrdir._path_for_remote_call(self._client)
1693
response = self._call("Repository.break_lock", path)
1694
except errors.UnknownSmartMethod:
1696
return self._real_repository.break_lock()
1697
if response != ('ok',):
1698
raise errors.UnexpectedSmartServerResponse(response)
1700
def _get_tarball(self, compression):
1701
"""Return a TemporaryFile containing a repository tarball.
1703
Returns None if the server does not support sending tarballs.
1706
path = self.bzrdir._path_for_remote_call(self._client)
1708
response, protocol = self._call_expecting_body(
1709
'Repository.tarball', path, compression)
1710
except errors.UnknownSmartMethod:
1711
protocol.cancel_read_body()
1713
if response[0] == 'ok':
1714
# Extract the tarball and return it
1715
t = tempfile.NamedTemporaryFile()
1716
# TODO: rpc layer should read directly into it...
1717
t.write(protocol.read_body_bytes())
1720
raise errors.UnexpectedSmartServerResponse(response)
1723
def sprout(self, to_bzrdir, revision_id=None):
1724
"""Create a descendent repository for new development.
1726
Unlike clone, this does not copy the settings of the repository.
1728
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1729
dest_repo.fetch(self, revision_id=revision_id)
1732
def _create_sprouting_repo(self, a_bzrdir, shared):
1733
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1734
# use target default format.
1735
dest_repo = a_bzrdir.create_repository()
1737
# Most control formats need the repository to be specifically
1738
# created, but on some old all-in-one formats it's not needed
1740
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1741
except errors.UninitializableFormat:
1742
dest_repo = a_bzrdir.open_repository()
1745
### These methods are just thin shims to the VFS object for now.
1748
def revision_tree(self, revision_id):
1749
revision_id = _mod_revision.ensure_null(revision_id)
1750
if revision_id == _mod_revision.NULL_REVISION:
1751
return InventoryRevisionTree(self,
1752
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1754
return list(self.revision_trees([revision_id]))[0]
1756
def get_serializer_format(self):
1757
path = self.bzrdir._path_for_remote_call(self._client)
1759
response = self._call('VersionedFileRepository.get_serializer_format',
1761
except errors.UnknownSmartMethod:
1763
return self._real_repository.get_serializer_format()
1764
if response[0] != 'ok':
1765
raise errors.UnexpectedSmartServerResponse(response)
1768
def get_commit_builder(self, branch, parents, config, timestamp=None,
1769
timezone=None, committer=None, revprops=None,
1770
revision_id=None, lossy=False):
1771
"""Obtain a CommitBuilder for this repository.
1773
:param branch: Branch to commit to.
1774
:param parents: Revision ids of the parents of the new revision.
1775
:param config: Configuration to use.
1776
:param timestamp: Optional timestamp recorded for commit.
1777
:param timezone: Optional timezone for timestamp.
1778
:param committer: Optional committer to set for commit.
1779
:param revprops: Optional dictionary of revision properties.
1780
:param revision_id: Optional revision id.
1781
:param lossy: Whether to discard data that can not be natively
1782
represented, when pushing to a foreign VCS
1784
if self._fallback_repositories and not self._format.supports_chks:
1785
raise errors.BzrError("Cannot commit directly to a stacked branch"
1786
" in pre-2a formats. See "
1787
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1788
if self._format.rich_root_data:
1789
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1791
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1792
result = commit_builder_kls(self, parents, config,
1793
timestamp, timezone, committer, revprops, revision_id,
1795
self.start_write_group()
1798
def add_fallback_repository(self, repository):
1799
"""Add a repository to use for looking up data not held locally.
1801
:param repository: A repository.
1803
if not self._format.supports_external_lookups:
1804
raise errors.UnstackableRepositoryFormat(
1805
self._format.network_name(), self.base)
1806
# We need to accumulate additional repositories here, to pass them in
1809
# Make the check before we lock: this raises an exception.
1810
self._check_fallback_repository(repository)
1811
if self.is_locked():
1812
# We will call fallback.unlock() when we transition to the unlocked
1813
# state, so always add a lock here. If a caller passes us a locked
1814
# repository, they are responsible for unlocking it later.
1815
repository.lock_read()
1816
self._fallback_repositories.append(repository)
1817
# If self._real_repository was parameterised already (e.g. because a
1818
# _real_branch had its get_stacked_on_url method called), then the
1819
# repository to be added may already be in the _real_repositories list.
1820
if self._real_repository is not None:
1821
fallback_locations = [repo.user_url for repo in
1822
self._real_repository._fallback_repositories]
1823
if repository.user_url not in fallback_locations:
1824
self._real_repository.add_fallback_repository(repository)
1826
def _check_fallback_repository(self, repository):
1827
"""Check that this repository can fallback to repository safely.
1829
Raise an error if not.
1831
:param repository: A repository to fallback to.
1833
return _mod_repository.InterRepository._assert_same_model(
1836
def add_inventory(self, revid, inv, parents):
1838
return self._real_repository.add_inventory(revid, inv, parents)
1840
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1841
parents, basis_inv=None, propagate_caches=False):
1843
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1844
delta, new_revision_id, parents, basis_inv=basis_inv,
1845
propagate_caches=propagate_caches)
1847
def add_revision(self, revision_id, rev, inv=None):
1848
_mod_revision.check_not_reserved_id(revision_id)
1849
key = (revision_id,)
1850
# check inventory present
1851
if not self.inventories.get_parent_map([key]):
1853
raise errors.WeaveRevisionNotPresent(revision_id,
1856
# yes, this is not suitable for adding with ghosts.
1857
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1860
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1861
self._add_revision(rev)
1863
def _add_revision(self, rev):
1864
if self._real_repository is not None:
1865
return self._real_repository._add_revision(rev)
1866
text = self._serializer.write_revision_to_string(rev)
1867
key = (rev.revision_id,)
1868
parents = tuple((parent,) for parent in rev.parent_ids)
1869
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1870
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1871
self._format, self._write_group_tokens)
1874
def get_inventory(self, revision_id):
1875
return list(self.iter_inventories([revision_id]))[0]
1877
def _iter_inventories_rpc(self, revision_ids, ordering):
1878
if ordering is None:
1879
ordering = 'unordered'
1880
path = self.bzrdir._path_for_remote_call(self._client)
1881
body = "\n".join(revision_ids)
1882
response_tuple, response_handler = (
1883
self._call_with_body_bytes_expecting_body(
1884
"VersionedFileRepository.get_inventories",
1885
(path, ordering), body))
1886
if response_tuple[0] != "ok":
1887
raise errors.UnexpectedSmartServerResponse(response_tuple)
1888
deserializer = inventory_delta.InventoryDeltaDeserializer()
1889
byte_stream = response_handler.read_streamed_body()
1890
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1892
# no results whatsoever
1894
src_format, stream = decoded
1895
if src_format.network_name() != self._format.network_name():
1896
raise AssertionError(
1897
"Mismatched RemoteRepository and stream src %r, %r" % (
1898
src_format.network_name(), self._format.network_name()))
1899
# ignore the src format, it's not really relevant
1900
prev_inv = Inventory(root_id=None,
1901
revision_id=_mod_revision.NULL_REVISION)
1902
# there should be just one substream, with inventory deltas
1903
substream_kind, substream = stream.next()
1904
if substream_kind != "inventory-deltas":
1905
raise AssertionError(
1906
"Unexpected stream %r received" % substream_kind)
1907
for record in substream:
1908
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1909
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1910
if parent_id != prev_inv.revision_id:
1911
raise AssertionError("invalid base %r != %r" % (parent_id,
1912
prev_inv.revision_id))
1913
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1914
yield inv, inv.revision_id
1917
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1919
return self._real_repository._iter_inventories(revision_ids, ordering)
1921
def iter_inventories(self, revision_ids, ordering=None):
1922
"""Get many inventories by revision_ids.
1924
This will buffer some or all of the texts used in constructing the
1925
inventories in memory, but will only parse a single inventory at a
1928
:param revision_ids: The expected revision ids of the inventories.
1929
:param ordering: optional ordering, e.g. 'topological'. If not
1930
specified, the order of revision_ids will be preserved (by
1931
buffering if necessary).
1932
:return: An iterator of inventories.
1934
if ((None in revision_ids)
1935
or (_mod_revision.NULL_REVISION in revision_ids)):
1936
raise ValueError('cannot get null revision inventory')
1937
for inv, revid in self._iter_inventories(revision_ids, ordering):
1939
raise errors.NoSuchRevision(self, revid)
1942
def _iter_inventories(self, revision_ids, ordering=None):
1943
if len(revision_ids) == 0:
1945
missing = set(revision_ids)
1946
if ordering is None:
1947
order_as_requested = True
1949
order = list(revision_ids)
1951
next_revid = order.pop()
1953
order_as_requested = False
1954
if ordering != 'unordered' and self._fallback_repositories:
1955
raise ValueError('unsupported ordering %r' % ordering)
1956
iter_inv_fns = [self._iter_inventories_rpc] + [
1957
fallback._iter_inventories for fallback in
1958
self._fallback_repositories]
1960
for iter_inv in iter_inv_fns:
1961
request = [revid for revid in revision_ids if revid in missing]
1962
for inv, revid in iter_inv(request, ordering):
1965
missing.remove(inv.revision_id)
1966
if ordering != 'unordered':
1970
if order_as_requested:
1971
# Yield as many results as we can while preserving order.
1972
while next_revid in invs:
1973
inv = invs.pop(next_revid)
1974
yield inv, inv.revision_id
1976
next_revid = order.pop()
1978
# We still want to fully consume the stream, just
1979
# in case it is not actually finished at this point
1982
except errors.UnknownSmartMethod:
1983
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
1987
if order_as_requested:
1988
if next_revid is not None:
1989
yield None, next_revid
1992
yield invs.get(revid), revid
1995
yield None, missing.pop()
1998
def get_revision(self, revision_id):
1999
return self.get_revisions([revision_id])[0]
2001
def get_transaction(self):
2003
return self._real_repository.get_transaction()
2006
def clone(self, a_bzrdir, revision_id=None):
2007
dest_repo = self._create_sprouting_repo(
2008
a_bzrdir, shared=self.is_shared())
2009
self.copy_content_into(dest_repo, revision_id)
2012
def make_working_trees(self):
2013
"""See Repository.make_working_trees"""
2014
path = self.bzrdir._path_for_remote_call(self._client)
2016
response = self._call('Repository.make_working_trees', path)
2017
except errors.UnknownSmartMethod:
2019
return self._real_repository.make_working_trees()
2020
if response[0] not in ('yes', 'no'):
2021
raise SmartProtocolError('unexpected response code %s' % (response,))
2022
return response[0] == 'yes'
2024
def refresh_data(self):
2025
"""Re-read any data needed to synchronise with disk.
2027
This method is intended to be called after another repository instance
2028
(such as one used by a smart server) has inserted data into the
2029
repository. On all repositories this will work outside of write groups.
2030
Some repository formats (pack and newer for bzrlib native formats)
2031
support refresh_data inside write groups. If called inside a write
2032
group on a repository that does not support refreshing in a write group
2033
IsInWriteGroupError will be raised.
2035
if self._real_repository is not None:
2036
self._real_repository.refresh_data()
2037
# Refresh the parents cache for this object
2038
self._unstacked_provider.disable_cache()
2039
self._unstacked_provider.enable_cache()
2041
def revision_ids_to_search_result(self, result_set):
2042
"""Convert a set of revision ids to a graph SearchResult."""
2043
result_parents = set()
2044
for parents in self.get_graph().get_parent_map(
2045
result_set).itervalues():
2046
result_parents.update(parents)
2047
included_keys = result_set.intersection(result_parents)
2048
start_keys = result_set.difference(included_keys)
2049
exclude_keys = result_parents.difference(result_set)
2050
result = vf_search.SearchResult(start_keys, exclude_keys,
2051
len(result_set), result_set)
2055
def search_missing_revision_ids(self, other,
2056
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2057
find_ghosts=True, revision_ids=None, if_present_ids=None,
2059
"""Return the revision ids that other has that this does not.
2061
These are returned in topological order.
2063
revision_id: only return revision ids included by revision_id.
2065
if symbol_versioning.deprecated_passed(revision_id):
2066
symbol_versioning.warn(
2067
'search_missing_revision_ids(revision_id=...) was '
2068
'deprecated in 2.4. Use revision_ids=[...] instead.',
2069
DeprecationWarning, stacklevel=2)
2070
if revision_ids is not None:
2071
raise AssertionError(
2072
'revision_ids is mutually exclusive with revision_id')
2073
if revision_id is not None:
2074
revision_ids = [revision_id]
2075
inter_repo = _mod_repository.InterRepository.get(other, self)
2076
return inter_repo.search_missing_revision_ids(
2077
find_ghosts=find_ghosts, revision_ids=revision_ids,
2078
if_present_ids=if_present_ids, limit=limit)
2080
def fetch(self, source, revision_id=None, find_ghosts=False,
2082
# No base implementation to use as RemoteRepository is not a subclass
2083
# of Repository; so this is a copy of Repository.fetch().
2084
if fetch_spec is not None and revision_id is not None:
2085
raise AssertionError(
2086
"fetch_spec and revision_id are mutually exclusive.")
2087
if self.is_in_write_group():
2088
raise errors.InternalBzrError(
2089
"May not fetch while in a write group.")
2090
# fast path same-url fetch operations
2091
if (self.has_same_location(source)
2092
and fetch_spec is None
2093
and self._has_same_fallbacks(source)):
2094
# check that last_revision is in 'from' and then return a
2096
if (revision_id is not None and
2097
not _mod_revision.is_null(revision_id)):
2098
self.get_revision(revision_id)
2100
# if there is no specific appropriate InterRepository, this will get
2101
# the InterRepository base class, which raises an
2102
# IncompatibleRepositories when asked to fetch.
2103
inter = _mod_repository.InterRepository.get(source, self)
2104
if (fetch_spec is not None and
2105
not getattr(inter, "supports_fetch_spec", False)):
2106
raise errors.UnsupportedOperation(
2107
"fetch_spec not supported for %r" % inter)
2108
return inter.fetch(revision_id=revision_id,
2109
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2111
def create_bundle(self, target, base, fileobj, format=None):
2113
self._real_repository.create_bundle(target, base, fileobj, format)
2116
@symbol_versioning.deprecated_method(
2117
symbol_versioning.deprecated_in((2, 4, 0)))
2118
def get_ancestry(self, revision_id, topo_sorted=True):
2120
return self._real_repository.get_ancestry(revision_id, topo_sorted)
2122
def fileids_altered_by_revision_ids(self, revision_ids):
2124
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2126
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2128
return self._real_repository._get_versioned_file_checker(
2129
revisions, revision_versions_cache)
2131
def _iter_files_bytes_rpc(self, desired_files, absent):
2132
path = self.bzrdir._path_for_remote_call(self._client)
2135
for (file_id, revid, identifier) in desired_files:
2136
lines.append("%s\0%s" % (
2137
osutils.safe_file_id(file_id),
2138
osutils.safe_revision_id(revid)))
2139
identifiers.append(identifier)
2140
(response_tuple, response_handler) = (
2141
self._call_with_body_bytes_expecting_body(
2142
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2143
if response_tuple != ('ok', ):
2144
response_handler.cancel_read_body()
2145
raise errors.UnexpectedSmartServerResponse(response_tuple)
2146
byte_stream = response_handler.read_streamed_body()
2147
def decompress_stream(start, byte_stream, unused):
2148
decompressor = zlib.decompressobj()
2149
yield decompressor.decompress(start)
2150
while decompressor.unused_data == "":
2152
data = byte_stream.next()
2153
except StopIteration:
2155
yield decompressor.decompress(data)
2156
yield decompressor.flush()
2157
unused.append(decompressor.unused_data)
2160
while not "\n" in unused:
2161
unused += byte_stream.next()
2162
header, rest = unused.split("\n", 1)
2163
args = header.split("\0")
2164
if args[0] == "absent":
2165
absent[identifiers[int(args[3])]] = (args[1], args[2])
2168
elif args[0] == "ok":
2171
raise errors.UnexpectedSmartServerResponse(args)
2173
yield (identifiers[idx],
2174
decompress_stream(rest, byte_stream, unused_chunks))
2175
unused = "".join(unused_chunks)
2177
def iter_files_bytes(self, desired_files):
2178
"""See Repository.iter_file_bytes.
2182
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2183
desired_files, absent):
2184
yield identifier, bytes_iterator
2185
for fallback in self._fallback_repositories:
2188
desired_files = [(key[0], key[1], identifier) for
2189
(identifier, key) in absent.iteritems()]
2190
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2191
del absent[identifier]
2192
yield identifier, bytes_iterator
2194
# There may be more missing items, but raise an exception
2196
missing_identifier = absent.keys()[0]
2197
missing_key = absent[missing_identifier]
2198
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2199
file_id=missing_key[0])
2200
except errors.UnknownSmartMethod:
2202
for (identifier, bytes_iterator) in (
2203
self._real_repository.iter_files_bytes(desired_files)):
2204
yield identifier, bytes_iterator
2206
def get_cached_parent_map(self, revision_ids):
2207
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2208
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2210
def get_parent_map(self, revision_ids):
2211
"""See bzrlib.Graph.get_parent_map()."""
2212
return self._make_parents_provider().get_parent_map(revision_ids)
2214
def _get_parent_map_rpc(self, keys):
2215
"""Helper for get_parent_map that performs the RPC."""
2216
medium = self._client._medium
2217
if medium._is_remote_before((1, 2)):
2218
# We already found out that the server can't understand
2219
# Repository.get_parent_map requests, so just fetch the whole
2222
# Note that this reads the whole graph, when only some keys are
2223
# wanted. On this old server there's no way (?) to get them all
2224
# in one go, and the user probably will have seen a warning about
2225
# the server being old anyhow.
2226
rg = self._get_revision_graph(None)
2227
# There is an API discrepancy between get_parent_map and
2228
# get_revision_graph. Specifically, a "key:()" pair in
2229
# get_revision_graph just means a node has no parents. For
2230
# "get_parent_map" it means the node is a ghost. So fix up the
2231
# graph to correct this.
2232
# https://bugs.launchpad.net/bzr/+bug/214894
2233
# There is one other "bug" which is that ghosts in
2234
# get_revision_graph() are not returned at all. But we won't worry
2235
# about that for now.
2236
for node_id, parent_ids in rg.iteritems():
2237
if parent_ids == ():
2238
rg[node_id] = (NULL_REVISION,)
2239
rg[NULL_REVISION] = ()
2244
raise ValueError('get_parent_map(None) is not valid')
2245
if NULL_REVISION in keys:
2246
keys.discard(NULL_REVISION)
2247
found_parents = {NULL_REVISION:()}
2249
return found_parents
2252
# TODO(Needs analysis): We could assume that the keys being requested
2253
# from get_parent_map are in a breadth first search, so typically they
2254
# will all be depth N from some common parent, and we don't have to
2255
# have the server iterate from the root parent, but rather from the
2256
# keys we're searching; and just tell the server the keyspace we
2257
# already have; but this may be more traffic again.
2259
# Transform self._parents_map into a search request recipe.
2260
# TODO: Manage this incrementally to avoid covering the same path
2261
# repeatedly. (The server will have to on each request, but the less
2262
# work done the better).
2264
# Negative caching notes:
2265
# new server sends missing when a request including the revid
2266
# 'include-missing:' is present in the request.
2267
# missing keys are serialised as missing:X, and we then call
2268
# provider.note_missing(X) for-all X
2269
parents_map = self._unstacked_provider.get_cached_map()
2270
if parents_map is None:
2271
# Repository is not locked, so there's no cache.
2273
if _DEFAULT_SEARCH_DEPTH <= 0:
2274
(start_set, stop_keys,
2275
key_count) = vf_search.search_result_from_parent_map(
2276
parents_map, self._unstacked_provider.missing_keys)
2278
(start_set, stop_keys,
2279
key_count) = vf_search.limited_search_result_from_parent_map(
2280
parents_map, self._unstacked_provider.missing_keys,
2281
keys, depth=_DEFAULT_SEARCH_DEPTH)
2282
recipe = ('manual', start_set, stop_keys, key_count)
2283
body = self._serialise_search_recipe(recipe)
2284
path = self.bzrdir._path_for_remote_call(self._client)
2286
if type(key) is not str:
2288
"key %r not a plain string" % (key,))
2289
verb = 'Repository.get_parent_map'
2290
args = (path, 'include-missing:') + tuple(keys)
2292
response = self._call_with_body_bytes_expecting_body(
2294
except errors.UnknownSmartMethod:
2295
# Server does not support this method, so get the whole graph.
2296
# Worse, we have to force a disconnection, because the server now
2297
# doesn't realise it has a body on the wire to consume, so the
2298
# only way to recover is to abandon the connection.
2300
'Server is too old for fast get_parent_map, reconnecting. '
2301
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2303
# To avoid having to disconnect repeatedly, we keep track of the
2304
# fact the server doesn't understand remote methods added in 1.2.
2305
medium._remember_remote_is_before((1, 2))
2306
# Recurse just once and we should use the fallback code.
2307
return self._get_parent_map_rpc(keys)
2308
response_tuple, response_handler = response
2309
if response_tuple[0] not in ['ok']:
2310
response_handler.cancel_read_body()
2311
raise errors.UnexpectedSmartServerResponse(response_tuple)
2312
if response_tuple[0] == 'ok':
2313
coded = bz2.decompress(response_handler.read_body_bytes())
2315
# no revisions found
2317
lines = coded.split('\n')
2320
d = tuple(line.split())
2322
revision_graph[d[0]] = d[1:]
2325
if d[0].startswith('missing:'):
2327
self._unstacked_provider.note_missing_key(revid)
2329
# no parents - so give the Graph result
2331
revision_graph[d[0]] = (NULL_REVISION,)
2332
return revision_graph
2335
def get_signature_text(self, revision_id):
2336
path = self.bzrdir._path_for_remote_call(self._client)
2338
response_tuple, response_handler = self._call_expecting_body(
2339
'Repository.get_revision_signature_text', path, revision_id)
2340
except errors.UnknownSmartMethod:
2342
return self._real_repository.get_signature_text(revision_id)
2343
except errors.NoSuchRevision, err:
2344
for fallback in self._fallback_repositories:
2346
return fallback.get_signature_text(revision_id)
2347
except errors.NoSuchRevision:
2351
if response_tuple[0] != 'ok':
2352
raise errors.UnexpectedSmartServerResponse(response_tuple)
2353
return response_handler.read_body_bytes()
2356
def _get_inventory_xml(self, revision_id):
2357
# This call is used by older working tree formats,
2358
# which stored a serialized basis inventory.
2360
return self._real_repository._get_inventory_xml(revision_id)
2363
def reconcile(self, other=None, thorough=False):
2364
from bzrlib.reconcile import RepoReconciler
2365
path = self.bzrdir._path_for_remote_call(self._client)
2367
response, handler = self._call_expecting_body(
2368
'Repository.reconcile', path, self._lock_token)
2369
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2371
return self._real_repository.reconcile(other=other, thorough=thorough)
2372
if response != ('ok', ):
2373
raise errors.UnexpectedSmartServerResponse(response)
2374
body = handler.read_body_bytes()
2375
result = RepoReconciler(self)
2376
for line in body.split('\n'):
2379
key, val_text = line.split(':')
2380
if key == "garbage_inventories":
2381
result.garbage_inventories = int(val_text)
2382
elif key == "inconsistent_parents":
2383
result.inconsistent_parents = int(val_text)
2385
mutter("unknown reconcile key %r" % key)
2388
def all_revision_ids(self):
2389
path = self.bzrdir._path_for_remote_call(self._client)
2391
response_tuple, response_handler = self._call_expecting_body(
2392
"Repository.all_revision_ids", path)
2393
except errors.UnknownSmartMethod:
2395
return self._real_repository.all_revision_ids()
2396
if response_tuple != ("ok", ):
2397
raise errors.UnexpectedSmartServerResponse(response_tuple)
2398
revids = set(response_handler.read_body_bytes().splitlines())
2399
for fallback in self._fallback_repositories:
2400
revids.update(set(fallback.all_revision_ids()))
2403
def _filtered_revision_trees(self, revision_ids, file_ids):
2404
"""Return Tree for a revision on this branch with only some files.
2406
:param revision_ids: a sequence of revision-ids;
2407
a revision-id may not be None or 'null:'
2408
:param file_ids: if not None, the result is filtered
2409
so that only those file-ids, their parents and their
2410
children are included.
2412
inventories = self.iter_inventories(revision_ids)
2413
for inv in inventories:
2414
# Should we introduce a FilteredRevisionTree class rather
2415
# than pre-filter the inventory here?
2416
filtered_inv = inv.filter(file_ids)
2417
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2420
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2421
medium = self._client._medium
2422
if medium._is_remote_before((1, 2)):
2424
for delta in self._real_repository.get_deltas_for_revisions(
2425
revisions, specific_fileids):
2428
# Get the revision-ids of interest
2429
required_trees = set()
2430
for revision in revisions:
2431
required_trees.add(revision.revision_id)
2432
required_trees.update(revision.parent_ids[:1])
2434
# Get the matching filtered trees. Note that it's more
2435
# efficient to pass filtered trees to changes_from() rather
2436
# than doing the filtering afterwards. changes_from() could
2437
# arguably do the filtering itself but it's path-based, not
2438
# file-id based, so filtering before or afterwards is
2440
if specific_fileids is None:
2441
trees = dict((t.get_revision_id(), t) for
2442
t in self.revision_trees(required_trees))
2444
trees = dict((t.get_revision_id(), t) for
2445
t in self._filtered_revision_trees(required_trees,
2448
# Calculate the deltas
2449
for revision in revisions:
2450
if not revision.parent_ids:
2451
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2453
old_tree = trees[revision.parent_ids[0]]
2454
yield trees[revision.revision_id].changes_from(old_tree)
2457
def get_revision_delta(self, revision_id, specific_fileids=None):
2458
r = self.get_revision(revision_id)
2459
return list(self.get_deltas_for_revisions([r],
2460
specific_fileids=specific_fileids))[0]
2463
def revision_trees(self, revision_ids):
2464
inventories = self.iter_inventories(revision_ids)
2465
for inv in inventories:
2466
yield InventoryRevisionTree(self, inv, inv.revision_id)
2469
def get_revision_reconcile(self, revision_id):
2471
return self._real_repository.get_revision_reconcile(revision_id)
2474
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2476
return self._real_repository.check(revision_ids=revision_ids,
2477
callback_refs=callback_refs, check_repo=check_repo)
2479
def copy_content_into(self, destination, revision_id=None):
2480
"""Make a complete copy of the content in self into destination.
2482
This is a destructive operation! Do not use it on existing
2485
interrepo = _mod_repository.InterRepository.get(self, destination)
2486
return interrepo.copy_content(revision_id)
2488
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2489
# get a tarball of the remote repository, and copy from that into the
2492
# TODO: Maybe a progress bar while streaming the tarball?
2493
note(gettext("Copying repository content as tarball..."))
2494
tar_file = self._get_tarball('bz2')
2495
if tar_file is None:
2497
destination = to_bzrdir.create_repository()
2499
tar = tarfile.open('repository', fileobj=tar_file,
2501
tmpdir = osutils.mkdtemp()
2503
_extract_tar(tar, tmpdir)
2504
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2505
tmp_repo = tmp_bzrdir.open_repository()
2506
tmp_repo.copy_content_into(destination, revision_id)
2508
osutils.rmtree(tmpdir)
2512
# TODO: Suggestion from john: using external tar is much faster than
2513
# python's tarfile library, but it may not work on windows.
2516
def inventories(self):
2517
"""Decorate the real repository for now.
2519
In the long term a full blown network facility is needed to
2520
avoid creating a real repository object locally.
2523
return self._real_repository.inventories
2526
def pack(self, hint=None, clean_obsolete_packs=False):
2527
"""Compress the data within the repository.
2532
body = "".join([l+"\n" for l in hint])
2533
path = self.bzrdir._path_for_remote_call(self._client)
2535
response, handler = self._call_with_body_bytes_expecting_body(
2536
'Repository.pack', (path, self._lock_token,
2537
str(clean_obsolete_packs)), body)
2538
except errors.UnknownSmartMethod:
2540
return self._real_repository.pack(hint=hint,
2541
clean_obsolete_packs=clean_obsolete_packs)
2542
handler.cancel_read_body()
2543
if response != ('ok', ):
2544
raise errors.UnexpectedSmartServerResponse(response)
2547
def revisions(self):
2548
"""Decorate the real repository for now.
2550
In the long term a full blown network facility is needed.
2553
return self._real_repository.revisions
2555
def set_make_working_trees(self, new_value):
2557
new_value_str = "True"
2559
new_value_str = "False"
2560
path = self.bzrdir._path_for_remote_call(self._client)
2562
response = self._call(
2563
'Repository.set_make_working_trees', path, new_value_str)
2564
except errors.UnknownSmartMethod:
2566
self._real_repository.set_make_working_trees(new_value)
2568
if response[0] != 'ok':
2569
raise errors.UnexpectedSmartServerResponse(response)
2572
def signatures(self):
2573
"""Decorate the real repository for now.
2575
In the long term a full blown network facility is needed to avoid
2576
creating a real repository object locally.
2579
return self._real_repository.signatures
2582
def sign_revision(self, revision_id, gpg_strategy):
2583
testament = _mod_testament.Testament.from_revision(self, revision_id)
2584
plaintext = testament.as_short_text()
2585
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2589
"""Decorate the real repository for now.
2591
In the long term a full blown network facility is needed to avoid
2592
creating a real repository object locally.
2595
return self._real_repository.texts
2597
def _iter_revisions_rpc(self, revision_ids):
2598
body = "\n".join(revision_ids)
2599
path = self.bzrdir._path_for_remote_call(self._client)
2600
response_tuple, response_handler = (
2601
self._call_with_body_bytes_expecting_body(
2602
"Repository.iter_revisions", (path, ), body))
2603
if response_tuple[0] != "ok":
2604
raise errors.UnexpectedSmartServerResponse(response_tuple)
2605
serializer_format = response_tuple[1]
2606
serializer = serializer_format_registry.get(serializer_format)
2607
byte_stream = response_handler.read_streamed_body()
2608
decompressor = zlib.decompressobj()
2610
for bytes in byte_stream:
2611
chunks.append(decompressor.decompress(bytes))
2612
if decompressor.unused_data != "":
2613
chunks.append(decompressor.flush())
2614
yield serializer.read_revision_from_string("".join(chunks))
2615
unused = decompressor.unused_data
2616
decompressor = zlib.decompressobj()
2617
chunks = [decompressor.decompress(unused)]
2618
chunks.append(decompressor.flush())
2619
text = "".join(chunks)
2621
yield serializer.read_revision_from_string("".join(chunks))
2624
def get_revisions(self, revision_ids):
2625
if revision_ids is None:
2626
revision_ids = self.all_revision_ids()
2628
for rev_id in revision_ids:
2629
if not rev_id or not isinstance(rev_id, basestring):
2630
raise errors.InvalidRevisionId(
2631
revision_id=rev_id, branch=self)
2633
missing = set(revision_ids)
2635
for rev in self._iter_revisions_rpc(revision_ids):
2636
missing.remove(rev.revision_id)
2637
revs[rev.revision_id] = rev
2638
except errors.UnknownSmartMethod:
2640
return self._real_repository.get_revisions(revision_ids)
2641
for fallback in self._fallback_repositories:
2644
for revid in list(missing):
2645
# XXX JRV 2011-11-20: It would be nice if there was a
2646
# public method on Repository that could be used to query
2647
# for revision objects *without* failing completely if one
2648
# was missing. There is VersionedFileRepository._iter_revisions,
2649
# but unfortunately that's private and not provided by
2650
# all repository implementations.
2652
revs[revid] = fallback.get_revision(revid)
2653
except errors.NoSuchRevision:
2656
missing.remove(revid)
2658
raise errors.NoSuchRevision(self, list(missing)[0])
2659
return [revs[revid] for revid in revision_ids]
2661
def supports_rich_root(self):
2662
return self._format.rich_root_data
2664
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2665
def iter_reverse_revision_history(self, revision_id):
2667
return self._real_repository.iter_reverse_revision_history(revision_id)
2670
def _serializer(self):
2671
return self._format._serializer
2674
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2675
signature = gpg_strategy.sign(plaintext)
2676
self.add_signature_text(revision_id, signature)
2678
def add_signature_text(self, revision_id, signature):
2679
if self._real_repository:
2680
# If there is a real repository the write group will
2681
# be in the real repository as well, so use that:
2683
return self._real_repository.add_signature_text(
2684
revision_id, signature)
2685
path = self.bzrdir._path_for_remote_call(self._client)
2686
response, handler = self._call_with_body_bytes_expecting_body(
2687
'Repository.add_signature_text', (path, self._lock_token,
2688
revision_id) + tuple(self._write_group_tokens), signature)
2689
handler.cancel_read_body()
2691
if response[0] != 'ok':
2692
raise errors.UnexpectedSmartServerResponse(response)
2693
self._write_group_tokens = response[1:]
2695
def has_signature_for_revision_id(self, revision_id):
2696
path = self.bzrdir._path_for_remote_call(self._client)
2698
response = self._call('Repository.has_signature_for_revision_id',
2700
except errors.UnknownSmartMethod:
2702
return self._real_repository.has_signature_for_revision_id(
2704
if response[0] not in ('yes', 'no'):
2705
raise SmartProtocolError('unexpected response code %s' % (response,))
2706
if response[0] == 'yes':
2708
for fallback in self._fallback_repositories:
2709
if fallback.has_signature_for_revision_id(revision_id):
2714
def verify_revision_signature(self, revision_id, gpg_strategy):
2715
if not self.has_signature_for_revision_id(revision_id):
2716
return gpg.SIGNATURE_NOT_SIGNED, None
2717
signature = self.get_signature_text(revision_id)
2719
testament = _mod_testament.Testament.from_revision(self, revision_id)
2720
plaintext = testament.as_short_text()
2722
return gpg_strategy.verify(signature, plaintext)
2724
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2726
return self._real_repository.item_keys_introduced_by(revision_ids,
2727
_files_pb=_files_pb)
2729
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2731
return self._real_repository._find_inconsistent_revision_parents(
2734
def _check_for_inconsistent_revision_parents(self):
2736
return self._real_repository._check_for_inconsistent_revision_parents()
2738
def _make_parents_provider(self, other=None):
2739
providers = [self._unstacked_provider]
2740
if other is not None:
2741
providers.insert(0, other)
2742
return graph.StackedParentsProvider(_LazyListJoin(
2743
providers, self._fallback_repositories))
2745
def _serialise_search_recipe(self, recipe):
2746
"""Serialise a graph search recipe.
2748
:param recipe: A search recipe (start, stop, count).
2749
:return: Serialised bytes.
2751
start_keys = ' '.join(recipe[1])
2752
stop_keys = ' '.join(recipe[2])
2753
count = str(recipe[3])
2754
return '\n'.join((start_keys, stop_keys, count))
2756
def _serialise_search_result(self, search_result):
2757
parts = search_result.get_network_struct()
2758
return '\n'.join(parts)
2761
path = self.bzrdir._path_for_remote_call(self._client)
2763
response = self._call('PackRepository.autopack', path)
2764
except errors.UnknownSmartMethod:
2766
self._real_repository._pack_collection.autopack()
2769
if response[0] != 'ok':
2770
raise errors.UnexpectedSmartServerResponse(response)
2773
class RemoteStreamSink(vf_repository.StreamSink):
2775
def _insert_real(self, stream, src_format, resume_tokens):
2776
self.target_repo._ensure_real()
2777
sink = self.target_repo._real_repository._get_sink()
2778
result = sink.insert_stream(stream, src_format, resume_tokens)
2780
self.target_repo.autopack()
2783
def insert_stream(self, stream, src_format, resume_tokens):
2784
target = self.target_repo
2785
target._unstacked_provider.missing_keys.clear()
2786
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2787
if target._lock_token:
2788
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2789
lock_args = (target._lock_token or '',)
2791
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2793
client = target._client
2794
medium = client._medium
2795
path = target.bzrdir._path_for_remote_call(client)
2796
# Probe for the verb to use with an empty stream before sending the
2797
# real stream to it. We do this both to avoid the risk of sending a
2798
# large request that is then rejected, and because we don't want to
2799
# implement a way to buffer, rewind, or restart the stream.
2801
for verb, required_version in candidate_calls:
2802
if medium._is_remote_before(required_version):
2805
# We've already done the probing (and set _is_remote_before) on
2806
# a previous insert.
2809
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2811
response = client.call_with_body_stream(
2812
(verb, path, '') + lock_args, byte_stream)
2813
except errors.UnknownSmartMethod:
2814
medium._remember_remote_is_before(required_version)
2820
return self._insert_real(stream, src_format, resume_tokens)
2821
self._last_inv_record = None
2822
self._last_substream = None
2823
if required_version < (1, 19):
2824
# Remote side doesn't support inventory deltas. Wrap the stream to
2825
# make sure we don't send any. If the stream contains inventory
2826
# deltas we'll interrupt the smart insert_stream request and
2828
stream = self._stop_stream_if_inventory_delta(stream)
2829
byte_stream = smart_repo._stream_to_byte_stream(
2831
resume_tokens = ' '.join(resume_tokens)
2832
response = client.call_with_body_stream(
2833
(verb, path, resume_tokens) + lock_args, byte_stream)
2834
if response[0][0] not in ('ok', 'missing-basis'):
2835
raise errors.UnexpectedSmartServerResponse(response)
2836
if self._last_substream is not None:
2837
# The stream included an inventory-delta record, but the remote
2838
# side isn't new enough to support them. So we need to send the
2839
# rest of the stream via VFS.
2840
self.target_repo.refresh_data()
2841
return self._resume_stream_with_vfs(response, src_format)
2842
if response[0][0] == 'missing-basis':
2843
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2844
resume_tokens = tokens
2845
return resume_tokens, set(missing_keys)
2847
self.target_repo.refresh_data()
2850
def _resume_stream_with_vfs(self, response, src_format):
2851
"""Resume sending a stream via VFS, first resending the record and
2852
substream that couldn't be sent via an insert_stream verb.
2854
if response[0][0] == 'missing-basis':
2855
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2856
# Ignore missing_keys, we haven't finished inserting yet
2859
def resume_substream():
2860
# Yield the substream that was interrupted.
2861
for record in self._last_substream:
2863
self._last_substream = None
2864
def resume_stream():
2865
# Finish sending the interrupted substream
2866
yield ('inventory-deltas', resume_substream())
2867
# Then simply continue sending the rest of the stream.
2868
for substream_kind, substream in self._last_stream:
2869
yield substream_kind, substream
2870
return self._insert_real(resume_stream(), src_format, tokens)
2872
def _stop_stream_if_inventory_delta(self, stream):
2873
"""Normally this just lets the original stream pass-through unchanged.
2875
However if any 'inventory-deltas' substream occurs it will stop
2876
streaming, and store the interrupted substream and stream in
2877
self._last_substream and self._last_stream so that the stream can be
2878
resumed by _resume_stream_with_vfs.
2881
stream_iter = iter(stream)
2882
for substream_kind, substream in stream_iter:
2883
if substream_kind == 'inventory-deltas':
2884
self._last_substream = substream
2885
self._last_stream = stream_iter
2888
yield substream_kind, substream
2891
class RemoteStreamSource(vf_repository.StreamSource):
2892
"""Stream data from a remote server."""
2894
def get_stream(self, search):
2895
if (self.from_repository._fallback_repositories and
2896
self.to_format._fetch_order == 'topological'):
2897
return self._real_stream(self.from_repository, search)
2900
repos = [self.from_repository]
2906
repos.extend(repo._fallback_repositories)
2907
sources.append(repo)
2908
return self.missing_parents_chain(search, sources)
2910
def get_stream_for_missing_keys(self, missing_keys):
2911
self.from_repository._ensure_real()
2912
real_repo = self.from_repository._real_repository
2913
real_source = real_repo._get_source(self.to_format)
2914
return real_source.get_stream_for_missing_keys(missing_keys)
2916
def _real_stream(self, repo, search):
2917
"""Get a stream for search from repo.
2919
This never called RemoteStreamSource.get_stream, and is a helper
2920
for RemoteStreamSource._get_stream to allow getting a stream
2921
reliably whether fallback back because of old servers or trying
2922
to stream from a non-RemoteRepository (which the stacked support
2925
source = repo._get_source(self.to_format)
2926
if isinstance(source, RemoteStreamSource):
2928
source = repo._real_repository._get_source(self.to_format)
2929
return source.get_stream(search)
2931
def _get_stream(self, repo, search):
2932
"""Core worker to get a stream from repo for search.
2934
This is used by both get_stream and the stacking support logic. It
2935
deliberately gets a stream for repo which does not need to be
2936
self.from_repository. In the event that repo is not Remote, or
2937
cannot do a smart stream, a fallback is made to the generic
2938
repository._get_stream() interface, via self._real_stream.
2940
In the event of stacking, streams from _get_stream will not
2941
contain all the data for search - this is normal (see get_stream).
2943
:param repo: A repository.
2944
:param search: A search.
2946
# Fallbacks may be non-smart
2947
if not isinstance(repo, RemoteRepository):
2948
return self._real_stream(repo, search)
2949
client = repo._client
2950
medium = client._medium
2951
path = repo.bzrdir._path_for_remote_call(client)
2952
search_bytes = repo._serialise_search_result(search)
2953
args = (path, self.to_format.network_name())
2955
('Repository.get_stream_1.19', (1, 19)),
2956
('Repository.get_stream', (1, 13))]
2959
for verb, version in candidate_verbs:
2960
if medium._is_remote_before(version):
2963
response = repo._call_with_body_bytes_expecting_body(
2964
verb, args, search_bytes)
2965
except errors.UnknownSmartMethod:
2966
medium._remember_remote_is_before(version)
2967
except errors.UnknownErrorFromSmartServer, e:
2968
if isinstance(search, vf_search.EverythingResult):
2969
error_verb = e.error_from_smart_server.error_verb
2970
if error_verb == 'BadSearch':
2971
# Pre-2.4 servers don't support this sort of search.
2972
# XXX: perhaps falling back to VFS on BadSearch is a
2973
# good idea in general? It might provide a little bit
2974
# of protection against client-side bugs.
2975
medium._remember_remote_is_before((2, 4))
2979
response_tuple, response_handler = response
2983
return self._real_stream(repo, search)
2984
if response_tuple[0] != 'ok':
2985
raise errors.UnexpectedSmartServerResponse(response_tuple)
2986
byte_stream = response_handler.read_streamed_body()
2987
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2988
self._record_counter)
2989
if src_format.network_name() != repo._format.network_name():
2990
raise AssertionError(
2991
"Mismatched RemoteRepository and stream src %r, %r" % (
2992
src_format.network_name(), repo._format.network_name()))
2995
def missing_parents_chain(self, search, sources):
2996
"""Chain multiple streams together to handle stacking.
2998
:param search: The overall search to satisfy with streams.
2999
:param sources: A list of Repository objects to query.
3001
self.from_serialiser = self.from_repository._format._serializer
3002
self.seen_revs = set()
3003
self.referenced_revs = set()
3004
# If there are heads in the search, or the key count is > 0, we are not
3006
while not search.is_empty() and len(sources) > 1:
3007
source = sources.pop(0)
3008
stream = self._get_stream(source, search)
3009
for kind, substream in stream:
3010
if kind != 'revisions':
3011
yield kind, substream
3013
yield kind, self.missing_parents_rev_handler(substream)
3014
search = search.refine(self.seen_revs, self.referenced_revs)
3015
self.seen_revs = set()
3016
self.referenced_revs = set()
3017
if not search.is_empty():
3018
for kind, stream in self._get_stream(sources[0], search):
3021
def missing_parents_rev_handler(self, substream):
3022
for content in substream:
3023
revision_bytes = content.get_bytes_as('fulltext')
3024
revision = self.from_serialiser.read_revision_from_string(
3026
self.seen_revs.add(content.key[-1])
3027
self.referenced_revs.update(revision.parent_ids)
3031
class RemoteBranchLockableFiles(LockableFiles):
3032
"""A 'LockableFiles' implementation that talks to a smart server.
3034
This is not a public interface class.
3037
def __init__(self, bzrdir, _client):
3038
self.bzrdir = bzrdir
3039
self._client = _client
3040
self._need_find_modes = True
3041
LockableFiles.__init__(
3042
self, bzrdir.get_branch_transport(None),
3043
'lock', lockdir.LockDir)
3045
def _find_modes(self):
3046
# RemoteBranches don't let the client set the mode of control files.
3047
self._dir_mode = None
3048
self._file_mode = None
3051
class RemoteBranchFormat(branch.BranchFormat):
3053
def __init__(self, network_name=None):
3054
super(RemoteBranchFormat, self).__init__()
3055
self._matchingbzrdir = RemoteBzrDirFormat()
3056
self._matchingbzrdir.set_branch_format(self)
3057
self._custom_format = None
3058
self._network_name = network_name
3060
def __eq__(self, other):
3061
return (isinstance(other, RemoteBranchFormat) and
3062
self.__dict__ == other.__dict__)
3064
def _ensure_real(self):
3065
if self._custom_format is None:
3067
self._custom_format = branch.network_format_registry.get(
3070
raise errors.UnknownFormatError(kind='branch',
3071
format=self._network_name)
3073
def get_format_description(self):
3075
return 'Remote: ' + self._custom_format.get_format_description()
3077
def network_name(self):
3078
return self._network_name
3080
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3081
return a_bzrdir.open_branch(name=name,
3082
ignore_fallbacks=ignore_fallbacks)
3084
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3085
# Initialisation when using a local bzrdir object, or a non-vfs init
3086
# method is not available on the server.
3087
# self._custom_format is always set - the start of initialize ensures
3089
if isinstance(a_bzrdir, RemoteBzrDir):
3090
a_bzrdir._ensure_real()
3091
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3092
name, append_revisions_only=append_revisions_only)
3094
# We assume the bzrdir is parameterised; it may not be.
3095
result = self._custom_format.initialize(a_bzrdir, name,
3096
append_revisions_only=append_revisions_only)
3097
if (isinstance(a_bzrdir, RemoteBzrDir) and
3098
not isinstance(result, RemoteBranch)):
3099
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3103
def initialize(self, a_bzrdir, name=None, repository=None,
3104
append_revisions_only=None):
3105
# 1) get the network name to use.
3106
if self._custom_format:
3107
network_name = self._custom_format.network_name()
3109
# Select the current bzrlib default and ask for that.
3110
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3111
reference_format = reference_bzrdir_format.get_branch_format()
3112
self._custom_format = reference_format
3113
network_name = reference_format.network_name()
3114
# Being asked to create on a non RemoteBzrDir:
3115
if not isinstance(a_bzrdir, RemoteBzrDir):
3116
return self._vfs_initialize(a_bzrdir, name=name,
3117
append_revisions_only=append_revisions_only)
3118
medium = a_bzrdir._client._medium
3119
if medium._is_remote_before((1, 13)):
3120
return self._vfs_initialize(a_bzrdir, name=name,
3121
append_revisions_only=append_revisions_only)
3122
# Creating on a remote bzr dir.
3123
# 2) try direct creation via RPC
3124
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3125
if name is not None:
3126
# XXX JRV20100304: Support creating colocated branches
3127
raise errors.NoColocatedBranchSupport(self)
3128
verb = 'BzrDir.create_branch'
3130
response = a_bzrdir._call(verb, path, network_name)
3131
except errors.UnknownSmartMethod:
3132
# Fallback - use vfs methods
3133
medium._remember_remote_is_before((1, 13))
3134
return self._vfs_initialize(a_bzrdir, name=name,
3135
append_revisions_only=append_revisions_only)
3136
if response[0] != 'ok':
3137
raise errors.UnexpectedSmartServerResponse(response)
3138
# Turn the response into a RemoteRepository object.
3139
format = RemoteBranchFormat(network_name=response[1])
3140
repo_format = response_tuple_to_repo_format(response[3:])
3141
repo_path = response[2]
3142
if repository is not None:
3143
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3144
url_diff = urlutils.relative_url(repository.user_url,
3147
raise AssertionError(
3148
'repository.user_url %r does not match URL from server '
3149
'response (%r + %r)'
3150
% (repository.user_url, a_bzrdir.user_url, repo_path))
3151
remote_repo = repository
3154
repo_bzrdir = a_bzrdir
3156
repo_bzrdir = RemoteBzrDir(
3157
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3159
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3160
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3161
format=format, setup_stacking=False, name=name)
3162
if append_revisions_only:
3163
remote_branch.set_append_revisions_only(append_revisions_only)
3164
# XXX: We know this is a new branch, so it must have revno 0, revid
3165
# NULL_REVISION. Creating the branch locked would make this be unable
3166
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3167
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3168
return remote_branch
3170
def make_tags(self, branch):
3172
return self._custom_format.make_tags(branch)
3174
def supports_tags(self):
3175
# Remote branches might support tags, but we won't know until we
3176
# access the real remote branch.
3178
return self._custom_format.supports_tags()
3180
def supports_stacking(self):
3182
return self._custom_format.supports_stacking()
3184
def supports_set_append_revisions_only(self):
3186
return self._custom_format.supports_set_append_revisions_only()
3188
def _use_default_local_heads_to_fetch(self):
3189
# If the branch format is a metadir format *and* its heads_to_fetch
3190
# implementation is not overridden vs the base class, we can use the
3191
# base class logic rather than use the heads_to_fetch RPC. This is
3192
# usually cheaper in terms of net round trips, as the last-revision and
3193
# tags info fetched is cached and would be fetched anyway.
3195
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3196
branch_class = self._custom_format._branch_class()
3197
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3198
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3203
class RemoteBranchStore(_mod_config.IniFileStore):
3204
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3206
Note that this is specific to bzr-based formats.
3209
def __init__(self, branch):
3210
super(RemoteBranchStore, self).__init__()
3211
self.branch = branch
3213
self._real_store = None
3215
def lock_write(self, token=None):
3216
return self.branch.lock_write(token)
3219
return self.branch.unlock()
3223
# We need to be able to override the undecorated implementation
3224
self.save_without_locking()
3226
def save_without_locking(self):
3227
super(RemoteBranchStore, self).save()
3229
def external_url(self):
3230
return self.branch.user_url
3232
def _load_content(self):
3233
path = self.branch._remote_path()
3235
response, handler = self.branch._call_expecting_body(
3236
'Branch.get_config_file', path)
3237
except errors.UnknownSmartMethod:
3239
return self._real_store._load_content()
3240
if len(response) and response[0] != 'ok':
3241
raise errors.UnexpectedSmartServerResponse(response)
3242
return handler.read_body_bytes()
3244
def _save_content(self, content):
3245
path = self.branch._remote_path()
3247
response, handler = self.branch._call_with_body_bytes_expecting_body(
3248
'Branch.put_config_file', (path,
3249
self.branch._lock_token, self.branch._repo_lock_token),
3251
except errors.UnknownSmartMethod:
3253
return self._real_store._save_content(content)
3254
handler.cancel_read_body()
3255
if response != ('ok', ):
3256
raise errors.UnexpectedSmartServerResponse(response)
3258
def _ensure_real(self):
3259
self.branch._ensure_real()
3260
if self._real_store is None:
3261
self._real_store = _mod_config.BranchStore(self.branch)
3264
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3265
"""Branch stored on a server accessed by HPSS RPC.
3267
At the moment most operations are mapped down to simple file operations.
3270
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3271
_client=None, format=None, setup_stacking=True, name=None,
3272
possible_transports=None):
3273
"""Create a RemoteBranch instance.
3275
:param real_branch: An optional local implementation of the branch
3276
format, usually accessing the data via the VFS.
3277
:param _client: Private parameter for testing.
3278
:param format: A RemoteBranchFormat object, None to create one
3279
automatically. If supplied it should have a network_name already
3281
:param setup_stacking: If True make an RPC call to determine the
3282
stacked (or not) status of the branch. If False assume the branch
3284
:param name: Colocated branch name
3286
# We intentionally don't call the parent class's __init__, because it
3287
# will try to assign to self.tags, which is a property in this subclass.
3288
# And the parent's __init__ doesn't do much anyway.
3289
self.bzrdir = remote_bzrdir
3290
if _client is not None:
3291
self._client = _client
3293
self._client = remote_bzrdir._client
3294
self.repository = remote_repository
3295
if real_branch is not None:
3296
self._real_branch = real_branch
3297
# Give the remote repository the matching real repo.
3298
real_repo = self._real_branch.repository
3299
if isinstance(real_repo, RemoteRepository):
3300
real_repo._ensure_real()
3301
real_repo = real_repo._real_repository
3302
self.repository._set_real_repository(real_repo)
3303
# Give the branch the remote repository to let fast-pathing happen.
3304
self._real_branch.repository = self.repository
3306
self._real_branch = None
3307
# Fill out expected attributes of branch for bzrlib API users.
3308
self._clear_cached_state()
3309
# TODO: deprecate self.base in favor of user_url
3310
self.base = self.bzrdir.user_url
3312
self._control_files = None
3313
self._lock_mode = None
3314
self._lock_token = None
3315
self._repo_lock_token = None
3316
self._lock_count = 0
3317
self._leave_lock = False
3318
# Setup a format: note that we cannot call _ensure_real until all the
3319
# attributes above are set: This code cannot be moved higher up in this
3322
self._format = RemoteBranchFormat()
3323
if real_branch is not None:
3324
self._format._network_name = \
3325
self._real_branch._format.network_name()
3327
self._format = format
3328
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3329
# branch.open_branch method.
3330
self._real_ignore_fallbacks = not setup_stacking
3331
if not self._format._network_name:
3332
# Did not get from open_branchV2 - old server.
3334
self._format._network_name = \
3335
self._real_branch._format.network_name()
3336
self.tags = self._format.make_tags(self)
3337
# The base class init is not called, so we duplicate this:
3338
hooks = branch.Branch.hooks['open']
3341
self._is_stacked = False
3343
self._setup_stacking(possible_transports)
3345
def _setup_stacking(self, possible_transports):
3346
# configure stacking into the remote repository, by reading it from
3349
fallback_url = self.get_stacked_on_url()
3350
except (errors.NotStacked, errors.UnstackableBranchFormat,
3351
errors.UnstackableRepositoryFormat), e:
3353
self._is_stacked = True
3354
if possible_transports is None:
3355
possible_transports = []
3357
possible_transports = list(possible_transports)
3358
possible_transports.append(self.bzrdir.root_transport)
3359
self._activate_fallback_location(fallback_url,
3360
possible_transports=possible_transports)
3362
def _get_config(self):
3363
return RemoteBranchConfig(self)
3365
def _get_config_store(self):
3366
return RemoteBranchStore(self)
3368
def _get_real_transport(self):
3369
# if we try vfs access, return the real branch's vfs transport
3371
return self._real_branch._transport
3373
_transport = property(_get_real_transport)
3376
return "%s(%s)" % (self.__class__.__name__, self.base)
3380
def _ensure_real(self):
3381
"""Ensure that there is a _real_branch set.
3383
Used before calls to self._real_branch.
3385
if self._real_branch is None:
3386
if not vfs.vfs_enabled():
3387
raise AssertionError('smart server vfs must be enabled '
3388
'to use vfs implementation')
3389
self.bzrdir._ensure_real()
3390
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3391
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3392
if self.repository._real_repository is None:
3393
# Give the remote repository the matching real repo.
3394
real_repo = self._real_branch.repository
3395
if isinstance(real_repo, RemoteRepository):
3396
real_repo._ensure_real()
3397
real_repo = real_repo._real_repository
3398
self.repository._set_real_repository(real_repo)
3399
# Give the real branch the remote repository to let fast-pathing
3401
self._real_branch.repository = self.repository
3402
if self._lock_mode == 'r':
3403
self._real_branch.lock_read()
3404
elif self._lock_mode == 'w':
3405
self._real_branch.lock_write(token=self._lock_token)
3407
def _translate_error(self, err, **context):
3408
self.repository._translate_error(err, branch=self, **context)
3410
def _clear_cached_state(self):
3411
super(RemoteBranch, self)._clear_cached_state()
3412
if self._real_branch is not None:
3413
self._real_branch._clear_cached_state()
3415
def _clear_cached_state_of_remote_branch_only(self):
3416
"""Like _clear_cached_state, but doesn't clear the cache of
3419
This is useful when falling back to calling a method of
3420
self._real_branch that changes state. In that case the underlying
3421
branch changes, so we need to invalidate this RemoteBranch's cache of
3422
it. However, there's no need to invalidate the _real_branch's cache
3423
too, in fact doing so might harm performance.
3425
super(RemoteBranch, self)._clear_cached_state()
3428
def control_files(self):
3429
# Defer actually creating RemoteBranchLockableFiles until its needed,
3430
# because it triggers an _ensure_real that we otherwise might not need.
3431
if self._control_files is None:
3432
self._control_files = RemoteBranchLockableFiles(
3433
self.bzrdir, self._client)
3434
return self._control_files
3436
def get_physical_lock_status(self):
3437
"""See Branch.get_physical_lock_status()."""
3439
response = self._client.call('Branch.get_physical_lock_status',
3440
self._remote_path())
3441
except errors.UnknownSmartMethod:
3443
return self._real_branch.get_physical_lock_status()
3444
if response[0] not in ('yes', 'no'):
3445
raise errors.UnexpectedSmartServerResponse(response)
3446
return (response[0] == 'yes')
3448
def get_stacked_on_url(self):
3449
"""Get the URL this branch is stacked against.
3451
:raises NotStacked: If the branch is not stacked.
3452
:raises UnstackableBranchFormat: If the branch does not support
3454
:raises UnstackableRepositoryFormat: If the repository does not support
3458
# there may not be a repository yet, so we can't use
3459
# self._translate_error, so we can't use self._call either.
3460
response = self._client.call('Branch.get_stacked_on_url',
3461
self._remote_path())
3462
except errors.ErrorFromSmartServer, err:
3463
# there may not be a repository yet, so we can't call through
3464
# its _translate_error
3465
_translate_error(err, branch=self)
3466
except errors.UnknownSmartMethod, err:
3468
return self._real_branch.get_stacked_on_url()
3469
if response[0] != 'ok':
3470
raise errors.UnexpectedSmartServerResponse(response)
3473
def set_stacked_on_url(self, url):
3474
branch.Branch.set_stacked_on_url(self, url)
3476
self._is_stacked = False
3478
self._is_stacked = True
3480
def _vfs_get_tags_bytes(self):
3482
return self._real_branch._get_tags_bytes()
3485
def _get_tags_bytes(self):
3486
if self._tags_bytes is None:
3487
self._tags_bytes = self._get_tags_bytes_via_hpss()
3488
return self._tags_bytes
3490
def _get_tags_bytes_via_hpss(self):
3491
medium = self._client._medium
3492
if medium._is_remote_before((1, 13)):
3493
return self._vfs_get_tags_bytes()
3495
response = self._call('Branch.get_tags_bytes', self._remote_path())
3496
except errors.UnknownSmartMethod:
3497
medium._remember_remote_is_before((1, 13))
3498
return self._vfs_get_tags_bytes()
3501
def _vfs_set_tags_bytes(self, bytes):
3503
return self._real_branch._set_tags_bytes(bytes)
3505
def _set_tags_bytes(self, bytes):
3506
if self.is_locked():
3507
self._tags_bytes = bytes
3508
medium = self._client._medium
3509
if medium._is_remote_before((1, 18)):
3510
self._vfs_set_tags_bytes(bytes)
3514
self._remote_path(), self._lock_token, self._repo_lock_token)
3515
response = self._call_with_body_bytes(
3516
'Branch.set_tags_bytes', args, bytes)
3517
except errors.UnknownSmartMethod:
3518
medium._remember_remote_is_before((1, 18))
3519
self._vfs_set_tags_bytes(bytes)
3521
def lock_read(self):
3522
"""Lock the branch for read operations.
3524
:return: A bzrlib.lock.LogicalLockResult.
3526
self.repository.lock_read()
3527
if not self._lock_mode:
3528
self._note_lock('r')
3529
self._lock_mode = 'r'
3530
self._lock_count = 1
3531
if self._real_branch is not None:
3532
self._real_branch.lock_read()
3534
self._lock_count += 1
3535
return lock.LogicalLockResult(self.unlock)
3537
def _remote_lock_write(self, token):
3539
branch_token = repo_token = ''
3541
branch_token = token
3542
repo_token = self.repository.lock_write().repository_token
3543
self.repository.unlock()
3544
err_context = {'token': token}
3546
response = self._call(
3547
'Branch.lock_write', self._remote_path(), branch_token,
3548
repo_token or '', **err_context)
3549
except errors.LockContention, e:
3550
# The LockContention from the server doesn't have any
3551
# information about the lock_url. We re-raise LockContention
3552
# with valid lock_url.
3553
raise errors.LockContention('(remote lock)',
3554
self.repository.base.split('.bzr/')[0])
3555
if response[0] != 'ok':
3556
raise errors.UnexpectedSmartServerResponse(response)
3557
ok, branch_token, repo_token = response
3558
return branch_token, repo_token
3560
def lock_write(self, token=None):
3561
if not self._lock_mode:
3562
self._note_lock('w')
3563
# Lock the branch and repo in one remote call.
3564
remote_tokens = self._remote_lock_write(token)
3565
self._lock_token, self._repo_lock_token = remote_tokens
3566
if not self._lock_token:
3567
raise SmartProtocolError('Remote server did not return a token!')
3568
# Tell the self.repository object that it is locked.
3569
self.repository.lock_write(
3570
self._repo_lock_token, _skip_rpc=True)
3572
if self._real_branch is not None:
3573
self._real_branch.lock_write(token=self._lock_token)
3574
if token is not None:
3575
self._leave_lock = True
3577
self._leave_lock = False
3578
self._lock_mode = 'w'
3579
self._lock_count = 1
3580
elif self._lock_mode == 'r':
3581
raise errors.ReadOnlyError(self)
3583
if token is not None:
3584
# A token was given to lock_write, and we're relocking, so
3585
# check that the given token actually matches the one we
3587
if token != self._lock_token:
3588
raise errors.TokenMismatch(token, self._lock_token)
3589
self._lock_count += 1
3590
# Re-lock the repository too.
3591
self.repository.lock_write(self._repo_lock_token)
3592
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3594
def _unlock(self, branch_token, repo_token):
3595
err_context = {'token': str((branch_token, repo_token))}
3596
response = self._call(
3597
'Branch.unlock', self._remote_path(), branch_token,
3598
repo_token or '', **err_context)
3599
if response == ('ok',):
3601
raise errors.UnexpectedSmartServerResponse(response)
3603
@only_raises(errors.LockNotHeld, errors.LockBroken)
3606
self._lock_count -= 1
3607
if not self._lock_count:
3608
self._clear_cached_state()
3609
mode = self._lock_mode
3610
self._lock_mode = None
3611
if self._real_branch is not None:
3612
if (not self._leave_lock and mode == 'w' and
3613
self._repo_lock_token):
3614
# If this RemoteBranch will remove the physical lock
3615
# for the repository, make sure the _real_branch
3616
# doesn't do it first. (Because the _real_branch's
3617
# repository is set to be the RemoteRepository.)
3618
self._real_branch.repository.leave_lock_in_place()
3619
self._real_branch.unlock()
3621
# Only write-locked branched need to make a remote method
3622
# call to perform the unlock.
3624
if not self._lock_token:
3625
raise AssertionError('Locked, but no token!')
3626
branch_token = self._lock_token
3627
repo_token = self._repo_lock_token
3628
self._lock_token = None
3629
self._repo_lock_token = None
3630
if not self._leave_lock:
3631
self._unlock(branch_token, repo_token)
3633
self.repository.unlock()
3635
def break_lock(self):
3637
response = self._call(
3638
'Branch.break_lock', self._remote_path())
3639
except errors.UnknownSmartMethod:
3641
return self._real_branch.break_lock()
3642
if response != ('ok',):
3643
raise errors.UnexpectedSmartServerResponse(response)
3645
def leave_lock_in_place(self):
3646
if not self._lock_token:
3647
raise NotImplementedError(self.leave_lock_in_place)
3648
self._leave_lock = True
3650
def dont_leave_lock_in_place(self):
3651
if not self._lock_token:
3652
raise NotImplementedError(self.dont_leave_lock_in_place)
3653
self._leave_lock = False
3656
def get_rev_id(self, revno, history=None):
3658
return _mod_revision.NULL_REVISION
3659
last_revision_info = self.last_revision_info()
3660
ok, result = self.repository.get_rev_id_for_revno(
3661
revno, last_revision_info)
3664
missing_parent = result[1]
3665
# Either the revision named by the server is missing, or its parent
3666
# is. Call get_parent_map to determine which, so that we report a
3668
parent_map = self.repository.get_parent_map([missing_parent])
3669
if missing_parent in parent_map:
3670
missing_parent = parent_map[missing_parent]
3671
raise errors.RevisionNotPresent(missing_parent, self.repository)
3673
def _read_last_revision_info(self):
3674
response = self._call('Branch.last_revision_info', self._remote_path())
3675
if response[0] != 'ok':
3676
raise SmartProtocolError('unexpected response code %s' % (response,))
3677
revno = int(response[1])
3678
last_revision = response[2]
3679
return (revno, last_revision)
3681
def _gen_revision_history(self):
3682
"""See Branch._gen_revision_history()."""
3683
if self._is_stacked:
3685
return self._real_branch._gen_revision_history()
3686
response_tuple, response_handler = self._call_expecting_body(
3687
'Branch.revision_history', self._remote_path())
3688
if response_tuple[0] != 'ok':
3689
raise errors.UnexpectedSmartServerResponse(response_tuple)
3690
result = response_handler.read_body_bytes().split('\x00')
3695
def _remote_path(self):
3696
return self.bzrdir._path_for_remote_call(self._client)
3698
def _set_last_revision_descendant(self, revision_id, other_branch,
3699
allow_diverged=False, allow_overwrite_descendant=False):
3700
# This performs additional work to meet the hook contract; while its
3701
# undesirable, we have to synthesise the revno to call the hook, and
3702
# not calling the hook is worse as it means changes can't be prevented.
3703
# Having calculated this though, we can't just call into
3704
# set_last_revision_info as a simple call, because there is a set_rh
3705
# hook that some folk may still be using.
3706
old_revno, old_revid = self.last_revision_info()
3707
history = self._lefthand_history(revision_id)
3708
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3709
err_context = {'other_branch': other_branch}
3710
response = self._call('Branch.set_last_revision_ex',
3711
self._remote_path(), self._lock_token, self._repo_lock_token,
3712
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3714
self._clear_cached_state()
3715
if len(response) != 3 and response[0] != 'ok':
3716
raise errors.UnexpectedSmartServerResponse(response)
3717
new_revno, new_revision_id = response[1:]
3718
self._last_revision_info_cache = new_revno, new_revision_id
3719
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3720
if self._real_branch is not None:
3721
cache = new_revno, new_revision_id
3722
self._real_branch._last_revision_info_cache = cache
3724
def _set_last_revision(self, revision_id):
3725
old_revno, old_revid = self.last_revision_info()
3726
# This performs additional work to meet the hook contract; while its
3727
# undesirable, we have to synthesise the revno to call the hook, and
3728
# not calling the hook is worse as it means changes can't be prevented.
3729
# Having calculated this though, we can't just call into
3730
# set_last_revision_info as a simple call, because there is a set_rh
3731
# hook that some folk may still be using.
3732
history = self._lefthand_history(revision_id)
3733
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3734
self._clear_cached_state()
3735
response = self._call('Branch.set_last_revision',
3736
self._remote_path(), self._lock_token, self._repo_lock_token,
3738
if response != ('ok',):
3739
raise errors.UnexpectedSmartServerResponse(response)
3740
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3742
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3744
def set_revision_history(self, rev_history):
3745
"""See Branch.set_revision_history."""
3746
self._set_revision_history(rev_history)
3749
def _set_revision_history(self, rev_history):
3750
# Send just the tip revision of the history; the server will generate
3751
# the full history from that. If the revision doesn't exist in this
3752
# branch, NoSuchRevision will be raised.
3753
if rev_history == []:
3756
rev_id = rev_history[-1]
3757
self._set_last_revision(rev_id)
3758
for hook in branch.Branch.hooks['set_rh']:
3759
hook(self, rev_history)
3760
self._cache_revision_history(rev_history)
3762
def _get_parent_location(self):
3763
medium = self._client._medium
3764
if medium._is_remote_before((1, 13)):
3765
return self._vfs_get_parent_location()
3767
response = self._call('Branch.get_parent', self._remote_path())
3768
except errors.UnknownSmartMethod:
3769
medium._remember_remote_is_before((1, 13))
3770
return self._vfs_get_parent_location()
3771
if len(response) != 1:
3772
raise errors.UnexpectedSmartServerResponse(response)
3773
parent_location = response[0]
3774
if parent_location == '':
3776
return parent_location
3778
def _vfs_get_parent_location(self):
3780
return self._real_branch._get_parent_location()
3782
def _set_parent_location(self, url):
3783
medium = self._client._medium
3784
if medium._is_remote_before((1, 15)):
3785
return self._vfs_set_parent_location(url)
3787
call_url = url or ''
3788
if type(call_url) is not str:
3789
raise AssertionError('url must be a str or None (%s)' % url)
3790
response = self._call('Branch.set_parent_location',
3791
self._remote_path(), self._lock_token, self._repo_lock_token,
3793
except errors.UnknownSmartMethod:
3794
medium._remember_remote_is_before((1, 15))
3795
return self._vfs_set_parent_location(url)
3797
raise errors.UnexpectedSmartServerResponse(response)
3799
def _vfs_set_parent_location(self, url):
3801
return self._real_branch._set_parent_location(url)
3804
def pull(self, source, overwrite=False, stop_revision=None,
3806
self._clear_cached_state_of_remote_branch_only()
3808
return self._real_branch.pull(
3809
source, overwrite=overwrite, stop_revision=stop_revision,
3810
_override_hook_target=self, **kwargs)
3813
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3815
return self._real_branch.push(
3816
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3817
_override_hook_source_branch=self)
3819
def is_locked(self):
3820
return self._lock_count >= 1
3823
def revision_id_to_dotted_revno(self, revision_id):
3824
"""Given a revision id, return its dotted revno.
3826
:return: a tuple like (1,) or (400,1,3).
3829
response = self._call('Branch.revision_id_to_revno',
3830
self._remote_path(), revision_id)
3831
except errors.UnknownSmartMethod:
3833
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3834
if response[0] == 'ok':
3835
return tuple([int(x) for x in response[1:]])
3837
raise errors.UnexpectedSmartServerResponse(response)
3840
def revision_id_to_revno(self, revision_id):
3841
"""Given a revision id on the branch mainline, return its revno.
3846
response = self._call('Branch.revision_id_to_revno',
3847
self._remote_path(), revision_id)
3848
except errors.UnknownSmartMethod:
3850
return self._real_branch.revision_id_to_revno(revision_id)
3851
if response[0] == 'ok':
3852
if len(response) == 2:
3853
return int(response[1])
3854
raise NoSuchRevision(self, revision_id)
3856
raise errors.UnexpectedSmartServerResponse(response)
3859
def set_last_revision_info(self, revno, revision_id):
3860
# XXX: These should be returned by the set_last_revision_info verb
3861
old_revno, old_revid = self.last_revision_info()
3862
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3863
if not revision_id or not isinstance(revision_id, basestring):
3864
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3866
response = self._call('Branch.set_last_revision_info',
3867
self._remote_path(), self._lock_token, self._repo_lock_token,
3868
str(revno), revision_id)
3869
except errors.UnknownSmartMethod:
3871
self._clear_cached_state_of_remote_branch_only()
3872
self._real_branch.set_last_revision_info(revno, revision_id)
3873
self._last_revision_info_cache = revno, revision_id
3875
if response == ('ok',):
3876
self._clear_cached_state()
3877
self._last_revision_info_cache = revno, revision_id
3878
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3879
# Update the _real_branch's cache too.
3880
if self._real_branch is not None:
3881
cache = self._last_revision_info_cache
3882
self._real_branch._last_revision_info_cache = cache
3884
raise errors.UnexpectedSmartServerResponse(response)
3887
def generate_revision_history(self, revision_id, last_rev=None,
3889
medium = self._client._medium
3890
if not medium._is_remote_before((1, 6)):
3891
# Use a smart method for 1.6 and above servers
3893
self._set_last_revision_descendant(revision_id, other_branch,
3894
allow_diverged=True, allow_overwrite_descendant=True)
3896
except errors.UnknownSmartMethod:
3897
medium._remember_remote_is_before((1, 6))
3898
self._clear_cached_state_of_remote_branch_only()
3899
self._set_revision_history(self._lefthand_history(revision_id,
3900
last_rev=last_rev,other_branch=other_branch))
3902
def set_push_location(self, location):
3904
return self._real_branch.set_push_location(location)
3906
def heads_to_fetch(self):
3907
if self._format._use_default_local_heads_to_fetch():
3908
# We recognise this format, and its heads-to-fetch implementation
3909
# is the default one (tip + tags). In this case it's cheaper to
3910
# just use the default implementation rather than a special RPC as
3911
# the tip and tags data is cached.
3912
return branch.Branch.heads_to_fetch(self)
3913
medium = self._client._medium
3914
if medium._is_remote_before((2, 4)):
3915
return self._vfs_heads_to_fetch()
3917
return self._rpc_heads_to_fetch()
3918
except errors.UnknownSmartMethod:
3919
medium._remember_remote_is_before((2, 4))
3920
return self._vfs_heads_to_fetch()
3922
def _rpc_heads_to_fetch(self):
3923
response = self._call('Branch.heads_to_fetch', self._remote_path())
3924
if len(response) != 2:
3925
raise errors.UnexpectedSmartServerResponse(response)
3926
must_fetch, if_present_fetch = response
3927
return set(must_fetch), set(if_present_fetch)
3929
def _vfs_heads_to_fetch(self):
3931
return self._real_branch.heads_to_fetch()
3934
class RemoteConfig(object):
3935
"""A Config that reads and writes from smart verbs.
3937
It is a low-level object that considers config data to be name/value pairs
3938
that may be associated with a section. Assigning meaning to the these
3939
values is done at higher levels like bzrlib.config.TreeConfig.
3942
def get_option(self, name, section=None, default=None):
3943
"""Return the value associated with a named option.
3945
:param name: The name of the value
3946
:param section: The section the option is in (if any)
3947
:param default: The value to return if the value is not set
3948
:return: The value or default value
3951
configobj = self._get_configobj()
3954
section_obj = configobj
3957
section_obj = configobj[section]
3960
if section_obj is None:
3963
value = section_obj.get(name, default)
3964
except errors.UnknownSmartMethod:
3965
value = self._vfs_get_option(name, section, default)
3966
for hook in _mod_config.OldConfigHooks['get']:
3967
hook(self, name, value)
3970
def _response_to_configobj(self, response):
3971
if len(response[0]) and response[0][0] != 'ok':
3972
raise errors.UnexpectedSmartServerResponse(response)
3973
lines = response[1].read_body_bytes().splitlines()
3974
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
3975
for hook in _mod_config.OldConfigHooks['load']:
3980
class RemoteBranchConfig(RemoteConfig):
3981
"""A RemoteConfig for Branches."""
3983
def __init__(self, branch):
3984
self._branch = branch
3986
def _get_configobj(self):
3987
path = self._branch._remote_path()
3988
response = self._branch._client.call_expecting_body(
3989
'Branch.get_config_file', path)
3990
return self._response_to_configobj(response)
3992
def set_option(self, value, name, section=None):
3993
"""Set the value associated with a named option.
3995
:param value: The value to set
3996
:param name: The name of the value to set
3997
:param section: The section the option is in (if any)
3999
medium = self._branch._client._medium
4000
if medium._is_remote_before((1, 14)):
4001
return self._vfs_set_option(value, name, section)
4002
if isinstance(value, dict):
4003
if medium._is_remote_before((2, 2)):
4004
return self._vfs_set_option(value, name, section)
4005
return self._set_config_option_dict(value, name, section)
4007
return self._set_config_option(value, name, section)
4009
def _set_config_option(self, value, name, section):
4011
path = self._branch._remote_path()
4012
response = self._branch._client.call('Branch.set_config_option',
4013
path, self._branch._lock_token, self._branch._repo_lock_token,
4014
value.encode('utf8'), name, section or '')
4015
except errors.UnknownSmartMethod:
4016
medium = self._branch._client._medium
4017
medium._remember_remote_is_before((1, 14))
4018
return self._vfs_set_option(value, name, section)
4020
raise errors.UnexpectedSmartServerResponse(response)
4022
def _serialize_option_dict(self, option_dict):
4024
for key, value in option_dict.items():
4025
if isinstance(key, unicode):
4026
key = key.encode('utf8')
4027
if isinstance(value, unicode):
4028
value = value.encode('utf8')
4029
utf8_dict[key] = value
4030
return bencode.bencode(utf8_dict)
4032
def _set_config_option_dict(self, value, name, section):
4034
path = self._branch._remote_path()
4035
serialised_dict = self._serialize_option_dict(value)
4036
response = self._branch._client.call(
4037
'Branch.set_config_option_dict',
4038
path, self._branch._lock_token, self._branch._repo_lock_token,
4039
serialised_dict, name, section or '')
4040
except errors.UnknownSmartMethod:
4041
medium = self._branch._client._medium
4042
medium._remember_remote_is_before((2, 2))
4043
return self._vfs_set_option(value, name, section)
4045
raise errors.UnexpectedSmartServerResponse(response)
4047
def _real_object(self):
4048
self._branch._ensure_real()
4049
return self._branch._real_branch
4051
def _vfs_set_option(self, value, name, section=None):
4052
return self._real_object()._get_config().set_option(
4053
value, name, section)
4056
class RemoteBzrDirConfig(RemoteConfig):
4057
"""A RemoteConfig for BzrDirs."""
4059
def __init__(self, bzrdir):
4060
self._bzrdir = bzrdir
4062
def _get_configobj(self):
4063
medium = self._bzrdir._client._medium
4064
verb = 'BzrDir.get_config_file'
4065
if medium._is_remote_before((1, 15)):
4066
raise errors.UnknownSmartMethod(verb)
4067
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4068
response = self._bzrdir._call_expecting_body(
4070
return self._response_to_configobj(response)
4072
def _vfs_get_option(self, name, section, default):
4073
return self._real_object()._get_config().get_option(
4074
name, section, default)
4076
def set_option(self, value, name, section=None):
4077
"""Set the value associated with a named option.
4079
:param value: The value to set
4080
:param name: The name of the value to set
4081
:param section: The section the option is in (if any)
4083
return self._real_object()._get_config().set_option(
4084
value, name, section)
4086
def _real_object(self):
4087
self._bzrdir._ensure_real()
4088
return self._bzrdir._real_bzrdir
4091
def _extract_tar(tar, to_dir):
4092
"""Extract all the contents of a tarfile object.
4094
A replacement for extractall, which is not present in python2.4
4097
tar.extract(tarinfo, to_dir)
4100
error_translators = registry.Registry()
4101
no_context_error_translators = registry.Registry()
4104
def _translate_error(err, **context):
4105
"""Translate an ErrorFromSmartServer into a more useful error.
4107
Possible context keys:
4115
If the error from the server doesn't match a known pattern, then
4116
UnknownErrorFromSmartServer is raised.
4120
return context[name]
4121
except KeyError, key_err:
4122
mutter('Missing key %r in context %r', key_err.args[0], context)
4125
"""Get the path from the context if present, otherwise use first error
4129
return context['path']
4130
except KeyError, key_err:
4132
return err.error_args[0]
4133
except IndexError, idx_err:
4135
'Missing key %r in context %r', key_err.args[0], context)
4139
translator = error_translators.get(err.error_verb)
4143
raise translator(err, find, get_path)
4145
translator = no_context_error_translators.get(err.error_verb)
4147
raise errors.UnknownErrorFromSmartServer(err)
4149
raise translator(err)
4152
error_translators.register('NoSuchRevision',
4153
lambda err, find, get_path: NoSuchRevision(
4154
find('branch'), err.error_args[0]))
4155
error_translators.register('nosuchrevision',
4156
lambda err, find, get_path: NoSuchRevision(
4157
find('repository'), err.error_args[0]))
4159
def _translate_nobranch_error(err, find, get_path):
4160
if len(err.error_args) >= 1:
4161
extra = err.error_args[0]
4164
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4167
error_translators.register('nobranch', _translate_nobranch_error)
4168
error_translators.register('norepository',
4169
lambda err, find, get_path: errors.NoRepositoryPresent(
4171
error_translators.register('UnlockableTransport',
4172
lambda err, find, get_path: errors.UnlockableTransport(
4173
find('bzrdir').root_transport))
4174
error_translators.register('TokenMismatch',
4175
lambda err, find, get_path: errors.TokenMismatch(
4176
find('token'), '(remote token)'))
4177
error_translators.register('Diverged',
4178
lambda err, find, get_path: errors.DivergedBranches(
4179
find('branch'), find('other_branch')))
4180
error_translators.register('NotStacked',
4181
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4183
def _translate_PermissionDenied(err, find, get_path):
4185
if len(err.error_args) >= 2:
4186
extra = err.error_args[1]
4189
return errors.PermissionDenied(path, extra=extra)
4191
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4192
error_translators.register('ReadError',
4193
lambda err, find, get_path: errors.ReadError(get_path()))
4194
error_translators.register('NoSuchFile',
4195
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4196
error_translators.register('TokenLockingNotSupported',
4197
lambda err, find, get_path: errors.TokenLockingNotSupported(
4198
find('repository')))
4199
error_translators.register('UnsuspendableWriteGroup',
4200
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4201
repository=find('repository')))
4202
error_translators.register('UnresumableWriteGroup',
4203
lambda err, find, get_path: errors.UnresumableWriteGroup(
4204
repository=find('repository'), write_groups=err.error_args[0],
4205
reason=err.error_args[1]))
4206
no_context_error_translators.register('IncompatibleRepositories',
4207
lambda err: errors.IncompatibleRepositories(
4208
err.error_args[0], err.error_args[1], err.error_args[2]))
4209
no_context_error_translators.register('LockContention',
4210
lambda err: errors.LockContention('(remote lock)'))
4211
no_context_error_translators.register('LockFailed',
4212
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4213
no_context_error_translators.register('TipChangeRejected',
4214
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4215
no_context_error_translators.register('UnstackableBranchFormat',
4216
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4217
no_context_error_translators.register('UnstackableRepositoryFormat',
4218
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4219
no_context_error_translators.register('FileExists',
4220
lambda err: errors.FileExists(err.error_args[0]))
4221
no_context_error_translators.register('DirectoryNotEmpty',
4222
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4224
def _translate_short_readv_error(err):
4225
args = err.error_args
4226
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4229
no_context_error_translators.register('ShortReadvError',
4230
_translate_short_readv_error)
4232
def _translate_unicode_error(err):
4233
encoding = str(err.error_args[0]) # encoding must always be a string
4234
val = err.error_args[1]
4235
start = int(err.error_args[2])
4236
end = int(err.error_args[3])
4237
reason = str(err.error_args[4]) # reason must always be a string
4238
if val.startswith('u:'):
4239
val = val[2:].decode('utf-8')
4240
elif val.startswith('s:'):
4241
val = val[2:].decode('base64')
4242
if err.error_verb == 'UnicodeDecodeError':
4243
raise UnicodeDecodeError(encoding, val, start, end, reason)
4244
elif err.error_verb == 'UnicodeEncodeError':
4245
raise UnicodeEncodeError(encoding, val, start, end, reason)
4247
no_context_error_translators.register('UnicodeEncodeError',
4248
_translate_unicode_error)
4249
no_context_error_translators.register('UnicodeDecodeError',
4250
_translate_unicode_error)
4251
no_context_error_translators.register('ReadOnlyError',
4252
lambda err: errors.TransportNotPossible('readonly transport'))
4253
no_context_error_translators.register('MemoryError',
4254
lambda err: errors.BzrError("remote server out of memory\n"
4255
"Retry non-remotely, or contact the server admin for details."))
4256
no_context_error_translators.register('RevisionNotPresent',
4257
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4259
no_context_error_translators.register('BzrCheckError',
4260
lambda err: errors.BzrCheckError(msg=err.error_args[0]))