1
# Copyright (C) 2006-2012 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
25
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 external_url(self):
3216
return self.branch.user_url
3218
def _load_content(self):
3219
path = self.branch._remote_path()
3221
response, handler = self.branch._call_expecting_body(
3222
'Branch.get_config_file', path)
3223
except errors.UnknownSmartMethod:
3225
return self._real_store._load_content()
3226
if len(response) and response[0] != 'ok':
3227
raise errors.UnexpectedSmartServerResponse(response)
3228
return handler.read_body_bytes()
3230
def _save_content(self, content):
3231
path = self.branch._remote_path()
3233
response, handler = self.branch._call_with_body_bytes_expecting_body(
3234
'Branch.put_config_file', (path,
3235
self.branch._lock_token, self.branch._repo_lock_token),
3237
except errors.UnknownSmartMethod:
3239
return self._real_store._save_content(content)
3240
handler.cancel_read_body()
3241
if response != ('ok', ):
3242
raise errors.UnexpectedSmartServerResponse(response)
3244
def _ensure_real(self):
3245
self.branch._ensure_real()
3246
if self._real_store is None:
3247
self._real_store = _mod_config.BranchStore(self.branch)
3250
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3251
"""Branch stored on a server accessed by HPSS RPC.
3253
At the moment most operations are mapped down to simple file operations.
3256
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3257
_client=None, format=None, setup_stacking=True, name=None,
3258
possible_transports=None):
3259
"""Create a RemoteBranch instance.
3261
:param real_branch: An optional local implementation of the branch
3262
format, usually accessing the data via the VFS.
3263
:param _client: Private parameter for testing.
3264
:param format: A RemoteBranchFormat object, None to create one
3265
automatically. If supplied it should have a network_name already
3267
:param setup_stacking: If True make an RPC call to determine the
3268
stacked (or not) status of the branch. If False assume the branch
3270
:param name: Colocated branch name
3272
# We intentionally don't call the parent class's __init__, because it
3273
# will try to assign to self.tags, which is a property in this subclass.
3274
# And the parent's __init__ doesn't do much anyway.
3275
self.bzrdir = remote_bzrdir
3276
if _client is not None:
3277
self._client = _client
3279
self._client = remote_bzrdir._client
3280
self.repository = remote_repository
3281
if real_branch is not None:
3282
self._real_branch = real_branch
3283
# Give the remote repository the matching real repo.
3284
real_repo = self._real_branch.repository
3285
if isinstance(real_repo, RemoteRepository):
3286
real_repo._ensure_real()
3287
real_repo = real_repo._real_repository
3288
self.repository._set_real_repository(real_repo)
3289
# Give the branch the remote repository to let fast-pathing happen.
3290
self._real_branch.repository = self.repository
3292
self._real_branch = None
3293
# Fill out expected attributes of branch for bzrlib API users.
3294
self._clear_cached_state()
3295
# TODO: deprecate self.base in favor of user_url
3296
self.base = self.bzrdir.user_url
3298
self._control_files = None
3299
self._lock_mode = None
3300
self._lock_token = None
3301
self._repo_lock_token = None
3302
self._lock_count = 0
3303
self._leave_lock = False
3304
self.conf_store = None
3305
# Setup a format: note that we cannot call _ensure_real until all the
3306
# attributes above are set: This code cannot be moved higher up in this
3309
self._format = RemoteBranchFormat()
3310
if real_branch is not None:
3311
self._format._network_name = \
3312
self._real_branch._format.network_name()
3314
self._format = format
3315
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3316
# branch.open_branch method.
3317
self._real_ignore_fallbacks = not setup_stacking
3318
if not self._format._network_name:
3319
# Did not get from open_branchV2 - old server.
3321
self._format._network_name = \
3322
self._real_branch._format.network_name()
3323
self.tags = self._format.make_tags(self)
3324
# The base class init is not called, so we duplicate this:
3325
hooks = branch.Branch.hooks['open']
3328
self._is_stacked = False
3330
self._setup_stacking(possible_transports)
3332
def _setup_stacking(self, possible_transports):
3333
# configure stacking into the remote repository, by reading it from
3336
fallback_url = self.get_stacked_on_url()
3337
except (errors.NotStacked, errors.UnstackableBranchFormat,
3338
errors.UnstackableRepositoryFormat), e:
3340
self._is_stacked = True
3341
if possible_transports is None:
3342
possible_transports = []
3344
possible_transports = list(possible_transports)
3345
possible_transports.append(self.bzrdir.root_transport)
3346
self._activate_fallback_location(fallback_url,
3347
possible_transports=possible_transports)
3349
def _get_config(self):
3350
return RemoteBranchConfig(self)
3352
def _get_config_store(self):
3353
if self.conf_store is None:
3354
self.conf_store = RemoteBranchStore(self)
3355
return self.conf_store
3357
def _get_real_transport(self):
3358
# if we try vfs access, return the real branch's vfs transport
3360
return self._real_branch._transport
3362
_transport = property(_get_real_transport)
3365
return "%s(%s)" % (self.__class__.__name__, self.base)
3369
def _ensure_real(self):
3370
"""Ensure that there is a _real_branch set.
3372
Used before calls to self._real_branch.
3374
if self._real_branch is None:
3375
if not vfs.vfs_enabled():
3376
raise AssertionError('smart server vfs must be enabled '
3377
'to use vfs implementation')
3378
self.bzrdir._ensure_real()
3379
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3380
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3381
# The remote branch and the real branch shares the same store. If
3382
# we don't, there will always be cases where one of the stores
3383
# doesn't see an update made on the other.
3384
self._real_branch.conf_store = self.conf_store
3385
if self.repository._real_repository is None:
3386
# Give the remote repository the matching real repo.
3387
real_repo = self._real_branch.repository
3388
if isinstance(real_repo, RemoteRepository):
3389
real_repo._ensure_real()
3390
real_repo = real_repo._real_repository
3391
self.repository._set_real_repository(real_repo)
3392
# Give the real branch the remote repository to let fast-pathing
3394
self._real_branch.repository = self.repository
3395
if self._lock_mode == 'r':
3396
self._real_branch.lock_read()
3397
elif self._lock_mode == 'w':
3398
self._real_branch.lock_write(token=self._lock_token)
3400
def _translate_error(self, err, **context):
3401
self.repository._translate_error(err, branch=self, **context)
3403
def _clear_cached_state(self):
3404
super(RemoteBranch, self)._clear_cached_state()
3405
if self._real_branch is not None:
3406
self._real_branch._clear_cached_state()
3408
def _clear_cached_state_of_remote_branch_only(self):
3409
"""Like _clear_cached_state, but doesn't clear the cache of
3412
This is useful when falling back to calling a method of
3413
self._real_branch that changes state. In that case the underlying
3414
branch changes, so we need to invalidate this RemoteBranch's cache of
3415
it. However, there's no need to invalidate the _real_branch's cache
3416
too, in fact doing so might harm performance.
3418
super(RemoteBranch, self)._clear_cached_state()
3421
def control_files(self):
3422
# Defer actually creating RemoteBranchLockableFiles until its needed,
3423
# because it triggers an _ensure_real that we otherwise might not need.
3424
if self._control_files is None:
3425
self._control_files = RemoteBranchLockableFiles(
3426
self.bzrdir, self._client)
3427
return self._control_files
3429
def get_physical_lock_status(self):
3430
"""See Branch.get_physical_lock_status()."""
3432
response = self._client.call('Branch.get_physical_lock_status',
3433
self._remote_path())
3434
except errors.UnknownSmartMethod:
3436
return self._real_branch.get_physical_lock_status()
3437
if response[0] not in ('yes', 'no'):
3438
raise errors.UnexpectedSmartServerResponse(response)
3439
return (response[0] == 'yes')
3441
def get_stacked_on_url(self):
3442
"""Get the URL this branch is stacked against.
3444
:raises NotStacked: If the branch is not stacked.
3445
:raises UnstackableBranchFormat: If the branch does not support
3447
:raises UnstackableRepositoryFormat: If the repository does not support
3451
# there may not be a repository yet, so we can't use
3452
# self._translate_error, so we can't use self._call either.
3453
response = self._client.call('Branch.get_stacked_on_url',
3454
self._remote_path())
3455
except errors.ErrorFromSmartServer, err:
3456
# there may not be a repository yet, so we can't call through
3457
# its _translate_error
3458
_translate_error(err, branch=self)
3459
except errors.UnknownSmartMethod, err:
3461
return self._real_branch.get_stacked_on_url()
3462
if response[0] != 'ok':
3463
raise errors.UnexpectedSmartServerResponse(response)
3466
def set_stacked_on_url(self, url):
3467
branch.Branch.set_stacked_on_url(self, url)
3468
# We need the stacked_on_url to be visible both locally (to not query
3469
# it repeatedly) and remotely (so smart verbs can get it server side)
3470
# Without the following line,
3471
# bzrlib.tests.per_branch.test_create_clone.TestCreateClone
3472
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3473
# fails for remote branches -- vila 2012-01-04
3474
self.conf_store.save_changes()
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
if self.conf_store is not None:
3609
self.conf_store.save_changes()
3610
self._clear_cached_state()
3611
mode = self._lock_mode
3612
self._lock_mode = None
3613
if self._real_branch is not None:
3614
if (not self._leave_lock and mode == 'w' and
3615
self._repo_lock_token):
3616
# If this RemoteBranch will remove the physical lock
3617
# for the repository, make sure the _real_branch
3618
# doesn't do it first. (Because the _real_branch's
3619
# repository is set to be the RemoteRepository.)
3620
self._real_branch.repository.leave_lock_in_place()
3621
self._real_branch.unlock()
3623
# Only write-locked branched need to make a remote method
3624
# call to perform the unlock.
3626
if not self._lock_token:
3627
raise AssertionError('Locked, but no token!')
3628
branch_token = self._lock_token
3629
repo_token = self._repo_lock_token
3630
self._lock_token = None
3631
self._repo_lock_token = None
3632
if not self._leave_lock:
3633
self._unlock(branch_token, repo_token)
3635
self.repository.unlock()
3637
def break_lock(self):
3639
response = self._call(
3640
'Branch.break_lock', self._remote_path())
3641
except errors.UnknownSmartMethod:
3643
return self._real_branch.break_lock()
3644
if response != ('ok',):
3645
raise errors.UnexpectedSmartServerResponse(response)
3647
def leave_lock_in_place(self):
3648
if not self._lock_token:
3649
raise NotImplementedError(self.leave_lock_in_place)
3650
self._leave_lock = True
3652
def dont_leave_lock_in_place(self):
3653
if not self._lock_token:
3654
raise NotImplementedError(self.dont_leave_lock_in_place)
3655
self._leave_lock = False
3658
def get_rev_id(self, revno, history=None):
3660
return _mod_revision.NULL_REVISION
3661
last_revision_info = self.last_revision_info()
3662
ok, result = self.repository.get_rev_id_for_revno(
3663
revno, last_revision_info)
3666
missing_parent = result[1]
3667
# Either the revision named by the server is missing, or its parent
3668
# is. Call get_parent_map to determine which, so that we report a
3670
parent_map = self.repository.get_parent_map([missing_parent])
3671
if missing_parent in parent_map:
3672
missing_parent = parent_map[missing_parent]
3673
raise errors.RevisionNotPresent(missing_parent, self.repository)
3675
def _read_last_revision_info(self):
3676
response = self._call('Branch.last_revision_info', self._remote_path())
3677
if response[0] != 'ok':
3678
raise SmartProtocolError('unexpected response code %s' % (response,))
3679
revno = int(response[1])
3680
last_revision = response[2]
3681
return (revno, last_revision)
3683
def _gen_revision_history(self):
3684
"""See Branch._gen_revision_history()."""
3685
if self._is_stacked:
3687
return self._real_branch._gen_revision_history()
3688
response_tuple, response_handler = self._call_expecting_body(
3689
'Branch.revision_history', self._remote_path())
3690
if response_tuple[0] != 'ok':
3691
raise errors.UnexpectedSmartServerResponse(response_tuple)
3692
result = response_handler.read_body_bytes().split('\x00')
3697
def _remote_path(self):
3698
return self.bzrdir._path_for_remote_call(self._client)
3700
def _set_last_revision_descendant(self, revision_id, other_branch,
3701
allow_diverged=False, allow_overwrite_descendant=False):
3702
# This performs additional work to meet the hook contract; while its
3703
# undesirable, we have to synthesise the revno to call the hook, and
3704
# not calling the hook is worse as it means changes can't be prevented.
3705
# Having calculated this though, we can't just call into
3706
# set_last_revision_info as a simple call, because there is a set_rh
3707
# hook that some folk may still be using.
3708
old_revno, old_revid = self.last_revision_info()
3709
history = self._lefthand_history(revision_id)
3710
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3711
err_context = {'other_branch': other_branch}
3712
response = self._call('Branch.set_last_revision_ex',
3713
self._remote_path(), self._lock_token, self._repo_lock_token,
3714
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3716
self._clear_cached_state()
3717
if len(response) != 3 and response[0] != 'ok':
3718
raise errors.UnexpectedSmartServerResponse(response)
3719
new_revno, new_revision_id = response[1:]
3720
self._last_revision_info_cache = new_revno, new_revision_id
3721
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3722
if self._real_branch is not None:
3723
cache = new_revno, new_revision_id
3724
self._real_branch._last_revision_info_cache = cache
3726
def _set_last_revision(self, revision_id):
3727
old_revno, old_revid = self.last_revision_info()
3728
# This performs additional work to meet the hook contract; while its
3729
# undesirable, we have to synthesise the revno to call the hook, and
3730
# not calling the hook is worse as it means changes can't be prevented.
3731
# Having calculated this though, we can't just call into
3732
# set_last_revision_info as a simple call, because there is a set_rh
3733
# hook that some folk may still be using.
3734
history = self._lefthand_history(revision_id)
3735
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3736
self._clear_cached_state()
3737
response = self._call('Branch.set_last_revision',
3738
self._remote_path(), self._lock_token, self._repo_lock_token,
3740
if response != ('ok',):
3741
raise errors.UnexpectedSmartServerResponse(response)
3742
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3744
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3746
def set_revision_history(self, rev_history):
3747
"""See Branch.set_revision_history."""
3748
self._set_revision_history(rev_history)
3751
def _set_revision_history(self, rev_history):
3752
# Send just the tip revision of the history; the server will generate
3753
# the full history from that. If the revision doesn't exist in this
3754
# branch, NoSuchRevision will be raised.
3755
if rev_history == []:
3758
rev_id = rev_history[-1]
3759
self._set_last_revision(rev_id)
3760
for hook in branch.Branch.hooks['set_rh']:
3761
hook(self, rev_history)
3762
self._cache_revision_history(rev_history)
3764
def _get_parent_location(self):
3765
medium = self._client._medium
3766
if medium._is_remote_before((1, 13)):
3767
return self._vfs_get_parent_location()
3769
response = self._call('Branch.get_parent', self._remote_path())
3770
except errors.UnknownSmartMethod:
3771
medium._remember_remote_is_before((1, 13))
3772
return self._vfs_get_parent_location()
3773
if len(response) != 1:
3774
raise errors.UnexpectedSmartServerResponse(response)
3775
parent_location = response[0]
3776
if parent_location == '':
3778
return parent_location
3780
def _vfs_get_parent_location(self):
3782
return self._real_branch._get_parent_location()
3784
def _set_parent_location(self, url):
3785
medium = self._client._medium
3786
if medium._is_remote_before((1, 15)):
3787
return self._vfs_set_parent_location(url)
3789
call_url = url or ''
3790
if type(call_url) is not str:
3791
raise AssertionError('url must be a str or None (%s)' % url)
3792
response = self._call('Branch.set_parent_location',
3793
self._remote_path(), self._lock_token, self._repo_lock_token,
3795
except errors.UnknownSmartMethod:
3796
medium._remember_remote_is_before((1, 15))
3797
return self._vfs_set_parent_location(url)
3799
raise errors.UnexpectedSmartServerResponse(response)
3801
def _vfs_set_parent_location(self, url):
3803
return self._real_branch._set_parent_location(url)
3806
def pull(self, source, overwrite=False, stop_revision=None,
3808
self._clear_cached_state_of_remote_branch_only()
3810
return self._real_branch.pull(
3811
source, overwrite=overwrite, stop_revision=stop_revision,
3812
_override_hook_target=self, **kwargs)
3815
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3817
return self._real_branch.push(
3818
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3819
_override_hook_source_branch=self)
3821
def is_locked(self):
3822
return self._lock_count >= 1
3825
def revision_id_to_dotted_revno(self, revision_id):
3826
"""Given a revision id, return its dotted revno.
3828
:return: a tuple like (1,) or (400,1,3).
3831
response = self._call('Branch.revision_id_to_revno',
3832
self._remote_path(), revision_id)
3833
except errors.UnknownSmartMethod:
3835
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3836
if response[0] == 'ok':
3837
return tuple([int(x) for x in response[1:]])
3839
raise errors.UnexpectedSmartServerResponse(response)
3842
def revision_id_to_revno(self, revision_id):
3843
"""Given a revision id on the branch mainline, return its revno.
3848
response = self._call('Branch.revision_id_to_revno',
3849
self._remote_path(), revision_id)
3850
except errors.UnknownSmartMethod:
3852
return self._real_branch.revision_id_to_revno(revision_id)
3853
if response[0] == 'ok':
3854
if len(response) == 2:
3855
return int(response[1])
3856
raise NoSuchRevision(self, revision_id)
3858
raise errors.UnexpectedSmartServerResponse(response)
3861
def set_last_revision_info(self, revno, revision_id):
3862
# XXX: These should be returned by the set_last_revision_info verb
3863
old_revno, old_revid = self.last_revision_info()
3864
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3865
if not revision_id or not isinstance(revision_id, basestring):
3866
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3868
response = self._call('Branch.set_last_revision_info',
3869
self._remote_path(), self._lock_token, self._repo_lock_token,
3870
str(revno), revision_id)
3871
except errors.UnknownSmartMethod:
3873
self._clear_cached_state_of_remote_branch_only()
3874
self._real_branch.set_last_revision_info(revno, revision_id)
3875
self._last_revision_info_cache = revno, revision_id
3877
if response == ('ok',):
3878
self._clear_cached_state()
3879
self._last_revision_info_cache = revno, revision_id
3880
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3881
# Update the _real_branch's cache too.
3882
if self._real_branch is not None:
3883
cache = self._last_revision_info_cache
3884
self._real_branch._last_revision_info_cache = cache
3886
raise errors.UnexpectedSmartServerResponse(response)
3889
def generate_revision_history(self, revision_id, last_rev=None,
3891
medium = self._client._medium
3892
if not medium._is_remote_before((1, 6)):
3893
# Use a smart method for 1.6 and above servers
3895
self._set_last_revision_descendant(revision_id, other_branch,
3896
allow_diverged=True, allow_overwrite_descendant=True)
3898
except errors.UnknownSmartMethod:
3899
medium._remember_remote_is_before((1, 6))
3900
self._clear_cached_state_of_remote_branch_only()
3901
self._set_revision_history(self._lefthand_history(revision_id,
3902
last_rev=last_rev,other_branch=other_branch))
3904
def set_push_location(self, location):
3905
self._set_config_location('push_location', location)
3907
def heads_to_fetch(self):
3908
if self._format._use_default_local_heads_to_fetch():
3909
# We recognise this format, and its heads-to-fetch implementation
3910
# is the default one (tip + tags). In this case it's cheaper to
3911
# just use the default implementation rather than a special RPC as
3912
# the tip and tags data is cached.
3913
return branch.Branch.heads_to_fetch(self)
3914
medium = self._client._medium
3915
if medium._is_remote_before((2, 4)):
3916
return self._vfs_heads_to_fetch()
3918
return self._rpc_heads_to_fetch()
3919
except errors.UnknownSmartMethod:
3920
medium._remember_remote_is_before((2, 4))
3921
return self._vfs_heads_to_fetch()
3923
def _rpc_heads_to_fetch(self):
3924
response = self._call('Branch.heads_to_fetch', self._remote_path())
3925
if len(response) != 2:
3926
raise errors.UnexpectedSmartServerResponse(response)
3927
must_fetch, if_present_fetch = response
3928
return set(must_fetch), set(if_present_fetch)
3930
def _vfs_heads_to_fetch(self):
3932
return self._real_branch.heads_to_fetch()
3935
class RemoteConfig(object):
3936
"""A Config that reads and writes from smart verbs.
3938
It is a low-level object that considers config data to be name/value pairs
3939
that may be associated with a section. Assigning meaning to the these
3940
values is done at higher levels like bzrlib.config.TreeConfig.
3943
def get_option(self, name, section=None, default=None):
3944
"""Return the value associated with a named option.
3946
:param name: The name of the value
3947
:param section: The section the option is in (if any)
3948
:param default: The value to return if the value is not set
3949
:return: The value or default value
3952
configobj = self._get_configobj()
3955
section_obj = configobj
3958
section_obj = configobj[section]
3961
if section_obj is None:
3964
value = section_obj.get(name, default)
3965
except errors.UnknownSmartMethod:
3966
value = self._vfs_get_option(name, section, default)
3967
for hook in _mod_config.OldConfigHooks['get']:
3968
hook(self, name, value)
3971
def _response_to_configobj(self, response):
3972
if len(response[0]) and response[0][0] != 'ok':
3973
raise errors.UnexpectedSmartServerResponse(response)
3974
lines = response[1].read_body_bytes().splitlines()
3975
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
3976
for hook in _mod_config.OldConfigHooks['load']:
3981
class RemoteBranchConfig(RemoteConfig):
3982
"""A RemoteConfig for Branches."""
3984
def __init__(self, branch):
3985
self._branch = branch
3987
def _get_configobj(self):
3988
path = self._branch._remote_path()
3989
response = self._branch._client.call_expecting_body(
3990
'Branch.get_config_file', path)
3991
return self._response_to_configobj(response)
3993
def set_option(self, value, name, section=None):
3994
"""Set the value associated with a named option.
3996
:param value: The value to set
3997
:param name: The name of the value to set
3998
:param section: The section the option is in (if any)
4000
medium = self._branch._client._medium
4001
if medium._is_remote_before((1, 14)):
4002
return self._vfs_set_option(value, name, section)
4003
if isinstance(value, dict):
4004
if medium._is_remote_before((2, 2)):
4005
return self._vfs_set_option(value, name, section)
4006
return self._set_config_option_dict(value, name, section)
4008
return self._set_config_option(value, name, section)
4010
def _set_config_option(self, value, name, section):
4012
path = self._branch._remote_path()
4013
response = self._branch._client.call('Branch.set_config_option',
4014
path, self._branch._lock_token, self._branch._repo_lock_token,
4015
value.encode('utf8'), name, section or '')
4016
except errors.UnknownSmartMethod:
4017
medium = self._branch._client._medium
4018
medium._remember_remote_is_before((1, 14))
4019
return self._vfs_set_option(value, name, section)
4021
raise errors.UnexpectedSmartServerResponse(response)
4023
def _serialize_option_dict(self, option_dict):
4025
for key, value in option_dict.items():
4026
if isinstance(key, unicode):
4027
key = key.encode('utf8')
4028
if isinstance(value, unicode):
4029
value = value.encode('utf8')
4030
utf8_dict[key] = value
4031
return bencode.bencode(utf8_dict)
4033
def _set_config_option_dict(self, value, name, section):
4035
path = self._branch._remote_path()
4036
serialised_dict = self._serialize_option_dict(value)
4037
response = self._branch._client.call(
4038
'Branch.set_config_option_dict',
4039
path, self._branch._lock_token, self._branch._repo_lock_token,
4040
serialised_dict, name, section or '')
4041
except errors.UnknownSmartMethod:
4042
medium = self._branch._client._medium
4043
medium._remember_remote_is_before((2, 2))
4044
return self._vfs_set_option(value, name, section)
4046
raise errors.UnexpectedSmartServerResponse(response)
4048
def _real_object(self):
4049
self._branch._ensure_real()
4050
return self._branch._real_branch
4052
def _vfs_set_option(self, value, name, section=None):
4053
return self._real_object()._get_config().set_option(
4054
value, name, section)
4057
class RemoteBzrDirConfig(RemoteConfig):
4058
"""A RemoteConfig for BzrDirs."""
4060
def __init__(self, bzrdir):
4061
self._bzrdir = bzrdir
4063
def _get_configobj(self):
4064
medium = self._bzrdir._client._medium
4065
verb = 'BzrDir.get_config_file'
4066
if medium._is_remote_before((1, 15)):
4067
raise errors.UnknownSmartMethod(verb)
4068
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4069
response = self._bzrdir._call_expecting_body(
4071
return self._response_to_configobj(response)
4073
def _vfs_get_option(self, name, section, default):
4074
return self._real_object()._get_config().get_option(
4075
name, section, default)
4077
def set_option(self, value, name, section=None):
4078
"""Set the value associated with a named option.
4080
:param value: The value to set
4081
:param name: The name of the value to set
4082
:param section: The section the option is in (if any)
4084
return self._real_object()._get_config().set_option(
4085
value, name, section)
4087
def _real_object(self):
4088
self._bzrdir._ensure_real()
4089
return self._bzrdir._real_bzrdir
4092
def _extract_tar(tar, to_dir):
4093
"""Extract all the contents of a tarfile object.
4095
A replacement for extractall, which is not present in python2.4
4098
tar.extract(tarinfo, to_dir)
4101
error_translators = registry.Registry()
4102
no_context_error_translators = registry.Registry()
4105
def _translate_error(err, **context):
4106
"""Translate an ErrorFromSmartServer into a more useful error.
4108
Possible context keys:
4116
If the error from the server doesn't match a known pattern, then
4117
UnknownErrorFromSmartServer is raised.
4121
return context[name]
4122
except KeyError, key_err:
4123
mutter('Missing key %r in context %r', key_err.args[0], context)
4126
"""Get the path from the context if present, otherwise use first error
4130
return context['path']
4131
except KeyError, key_err:
4133
return err.error_args[0]
4134
except IndexError, idx_err:
4136
'Missing key %r in context %r', key_err.args[0], context)
4140
translator = error_translators.get(err.error_verb)
4144
raise translator(err, find, get_path)
4146
translator = no_context_error_translators.get(err.error_verb)
4148
raise errors.UnknownErrorFromSmartServer(err)
4150
raise translator(err)
4153
error_translators.register('NoSuchRevision',
4154
lambda err, find, get_path: NoSuchRevision(
4155
find('branch'), err.error_args[0]))
4156
error_translators.register('nosuchrevision',
4157
lambda err, find, get_path: NoSuchRevision(
4158
find('repository'), err.error_args[0]))
4160
def _translate_nobranch_error(err, find, get_path):
4161
if len(err.error_args) >= 1:
4162
extra = err.error_args[0]
4165
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4168
error_translators.register('nobranch', _translate_nobranch_error)
4169
error_translators.register('norepository',
4170
lambda err, find, get_path: errors.NoRepositoryPresent(
4172
error_translators.register('UnlockableTransport',
4173
lambda err, find, get_path: errors.UnlockableTransport(
4174
find('bzrdir').root_transport))
4175
error_translators.register('TokenMismatch',
4176
lambda err, find, get_path: errors.TokenMismatch(
4177
find('token'), '(remote token)'))
4178
error_translators.register('Diverged',
4179
lambda err, find, get_path: errors.DivergedBranches(
4180
find('branch'), find('other_branch')))
4181
error_translators.register('NotStacked',
4182
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4184
def _translate_PermissionDenied(err, find, get_path):
4186
if len(err.error_args) >= 2:
4187
extra = err.error_args[1]
4190
return errors.PermissionDenied(path, extra=extra)
4192
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4193
error_translators.register('ReadError',
4194
lambda err, find, get_path: errors.ReadError(get_path()))
4195
error_translators.register('NoSuchFile',
4196
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4197
error_translators.register('TokenLockingNotSupported',
4198
lambda err, find, get_path: errors.TokenLockingNotSupported(
4199
find('repository')))
4200
error_translators.register('UnsuspendableWriteGroup',
4201
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4202
repository=find('repository')))
4203
error_translators.register('UnresumableWriteGroup',
4204
lambda err, find, get_path: errors.UnresumableWriteGroup(
4205
repository=find('repository'), write_groups=err.error_args[0],
4206
reason=err.error_args[1]))
4207
no_context_error_translators.register('IncompatibleRepositories',
4208
lambda err: errors.IncompatibleRepositories(
4209
err.error_args[0], err.error_args[1], err.error_args[2]))
4210
no_context_error_translators.register('LockContention',
4211
lambda err: errors.LockContention('(remote lock)'))
4212
no_context_error_translators.register('LockFailed',
4213
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4214
no_context_error_translators.register('TipChangeRejected',
4215
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4216
no_context_error_translators.register('UnstackableBranchFormat',
4217
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4218
no_context_error_translators.register('UnstackableRepositoryFormat',
4219
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4220
no_context_error_translators.register('FileExists',
4221
lambda err: errors.FileExists(err.error_args[0]))
4222
no_context_error_translators.register('DirectoryNotEmpty',
4223
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4225
def _translate_short_readv_error(err):
4226
args = err.error_args
4227
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4230
no_context_error_translators.register('ShortReadvError',
4231
_translate_short_readv_error)
4233
def _translate_unicode_error(err):
4234
encoding = str(err.error_args[0]) # encoding must always be a string
4235
val = err.error_args[1]
4236
start = int(err.error_args[2])
4237
end = int(err.error_args[3])
4238
reason = str(err.error_args[4]) # reason must always be a string
4239
if val.startswith('u:'):
4240
val = val[2:].decode('utf-8')
4241
elif val.startswith('s:'):
4242
val = val[2:].decode('base64')
4243
if err.error_verb == 'UnicodeDecodeError':
4244
raise UnicodeDecodeError(encoding, val, start, end, reason)
4245
elif err.error_verb == 'UnicodeEncodeError':
4246
raise UnicodeEncodeError(encoding, val, start, end, reason)
4248
no_context_error_translators.register('UnicodeEncodeError',
4249
_translate_unicode_error)
4250
no_context_error_translators.register('UnicodeDecodeError',
4251
_translate_unicode_error)
4252
no_context_error_translators.register('ReadOnlyError',
4253
lambda err: errors.TransportNotPossible('readonly transport'))
4254
no_context_error_translators.register('MemoryError',
4255
lambda err: errors.BzrError("remote server out of memory\n"
4256
"Retry non-remotely, or contact the server admin for details."))
4257
no_context_error_translators.register('RevisionNotPresent',
4258
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4260
no_context_error_translators.register('BzrCheckError',
4261
lambda err: errors.BzrCheckError(msg=err.error_args[0]))