1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
25
bzrdir as _mod_bzrdir,
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
64
_DEFAULT_SEARCH_DEPTH = 100
67
class _RpcHelper(object):
68
"""Mixin class that helps with issuing RPCs."""
70
def _call(self, method, *args, **err_context):
72
return self._client.call(method, *args)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
76
def _call_expecting_body(self, method, *args, **err_context):
78
return self._client.call_expecting_body(method, *args)
79
except errors.ErrorFromSmartServer, err:
80
self._translate_error(err, **err_context)
82
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
84
return self._client.call_with_body_bytes(method, args, body_bytes)
85
except errors.ErrorFromSmartServer, err:
86
self._translate_error(err, **err_context)
88
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
91
return self._client.call_with_body_bytes_expecting_body(
92
method, args, body_bytes)
93
except errors.ErrorFromSmartServer, err:
94
self._translate_error(err, **err_context)
97
def response_tuple_to_repo_format(response):
98
"""Convert a response tuple describing a repository format to a format."""
99
format = RemoteRepositoryFormat()
100
format._rich_root_data = (response[0] == 'yes')
101
format._supports_tree_reference = (response[1] == 'yes')
102
format._supports_external_lookups = (response[2] == 'yes')
103
format._network_name = response[3]
107
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
108
# does not have to be imported unless a remote format is involved.
110
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
111
"""Format representing bzrdirs accessed via a smart server"""
113
supports_workingtrees = False
116
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
117
# XXX: It's a bit ugly that the network name is here, because we'd
118
# like to believe that format objects are stateless or at least
119
# immutable, However, we do at least avoid mutating the name after
120
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
121
self._network_name = None
124
return "%s(_network_name=%r)" % (self.__class__.__name__,
127
def get_format_description(self):
128
if self._network_name:
130
real_format = controldir.network_format_registry.get(
135
return 'Remote: ' + real_format.get_format_description()
136
return 'bzr remote bzrdir'
138
def get_format_string(self):
139
raise NotImplementedError(self.get_format_string)
141
def network_name(self):
142
if self._network_name:
143
return self._network_name
145
raise AssertionError("No network name set.")
147
def initialize_on_transport(self, transport):
149
# hand off the request to the smart server
150
client_medium = transport.get_smart_medium()
151
except errors.NoSmartMedium:
152
# TODO: lookup the local format from a server hint.
153
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
154
return local_dir_format.initialize_on_transport(transport)
155
client = _SmartClient(client_medium)
156
path = client.remote_path_from_transport(transport)
158
response = client.call('BzrDirFormat.initialize', path)
159
except errors.ErrorFromSmartServer, err:
160
_translate_error(err, path=path)
161
if response[0] != 'ok':
162
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
163
format = RemoteBzrDirFormat()
164
self._supply_sub_formats_to(format)
165
return RemoteBzrDir(transport, format)
167
def parse_NoneTrueFalse(self, arg):
174
raise AssertionError("invalid arg %r" % arg)
176
def _serialize_NoneTrueFalse(self, arg):
183
def _serialize_NoneString(self, arg):
186
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
187
create_prefix=False, force_new_repo=False, stacked_on=None,
188
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
191
# hand off the request to the smart server
192
client_medium = transport.get_smart_medium()
193
except errors.NoSmartMedium:
196
# Decline to open it if the server doesn't support our required
197
# version (3) so that the VFS-based transport will do it.
198
if client_medium.should_probe():
200
server_version = client_medium.protocol_version()
201
if server_version != '2':
205
except errors.SmartProtocolError:
206
# Apparently there's no usable smart server there, even though
207
# the medium supports the smart protocol.
212
client = _SmartClient(client_medium)
213
path = client.remote_path_from_transport(transport)
214
if client_medium._is_remote_before((1, 16)):
217
# TODO: lookup the local format from a server hint.
218
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
219
self._supply_sub_formats_to(local_dir_format)
220
return local_dir_format.initialize_on_transport_ex(transport,
221
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
222
force_new_repo=force_new_repo, stacked_on=stacked_on,
223
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
224
make_working_trees=make_working_trees, shared_repo=shared_repo,
226
return self._initialize_on_transport_ex_rpc(client, path, transport,
227
use_existing_dir, create_prefix, force_new_repo, stacked_on,
228
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
230
def _initialize_on_transport_ex_rpc(self, client, path, transport,
231
use_existing_dir, create_prefix, force_new_repo, stacked_on,
232
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
234
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
235
args.append(self._serialize_NoneTrueFalse(create_prefix))
236
args.append(self._serialize_NoneTrueFalse(force_new_repo))
237
args.append(self._serialize_NoneString(stacked_on))
238
# stack_on_pwd is often/usually our transport
241
stack_on_pwd = transport.relpath(stack_on_pwd)
244
except errors.PathNotChild:
246
args.append(self._serialize_NoneString(stack_on_pwd))
247
args.append(self._serialize_NoneString(repo_format_name))
248
args.append(self._serialize_NoneTrueFalse(make_working_trees))
249
args.append(self._serialize_NoneTrueFalse(shared_repo))
250
request_network_name = self._network_name or \
251
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
253
response = client.call('BzrDirFormat.initialize_ex_1.16',
254
request_network_name, path, *args)
255
except errors.UnknownSmartMethod:
256
client._medium._remember_remote_is_before((1,16))
257
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
258
self._supply_sub_formats_to(local_dir_format)
259
return local_dir_format.initialize_on_transport_ex(transport,
260
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
261
force_new_repo=force_new_repo, stacked_on=stacked_on,
262
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
263
make_working_trees=make_working_trees, shared_repo=shared_repo,
265
except errors.ErrorFromSmartServer, err:
266
_translate_error(err, path=path)
267
repo_path = response[0]
268
bzrdir_name = response[6]
269
require_stacking = response[7]
270
require_stacking = self.parse_NoneTrueFalse(require_stacking)
271
format = RemoteBzrDirFormat()
272
format._network_name = bzrdir_name
273
self._supply_sub_formats_to(format)
274
bzrdir = RemoteBzrDir(transport, format, _client=client)
276
repo_format = response_tuple_to_repo_format(response[1:])
280
repo_bzrdir_format = RemoteBzrDirFormat()
281
repo_bzrdir_format._network_name = response[5]
282
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
286
final_stack = response[8] or None
287
final_stack_pwd = response[9] or None
289
final_stack_pwd = urlutils.join(
290
transport.base, final_stack_pwd)
291
remote_repo = RemoteRepository(repo_bzr, repo_format)
292
if len(response) > 10:
293
# Updated server verb that locks remotely.
294
repo_lock_token = response[10] or None
295
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
297
remote_repo.dont_leave_lock_in_place()
299
remote_repo.lock_write()
300
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
301
final_stack_pwd, require_stacking)
302
policy.acquire_repository()
306
bzrdir._format.set_branch_format(self.get_branch_format())
308
# The repo has already been created, but we need to make sure that
309
# we'll make a stackable branch.
310
bzrdir._format.require_stacking(_skip_repo=True)
311
return remote_repo, bzrdir, require_stacking, policy
313
def _open(self, transport):
314
return RemoteBzrDir(transport, self)
316
def __eq__(self, other):
317
if not isinstance(other, RemoteBzrDirFormat):
319
return self.get_format_description() == other.get_format_description()
321
def __return_repository_format(self):
322
# Always return a RemoteRepositoryFormat object, but if a specific bzr
323
# repository format has been asked for, tell the RemoteRepositoryFormat
324
# that it should use that for init() etc.
325
result = RemoteRepositoryFormat()
326
custom_format = getattr(self, '_repository_format', None)
328
if isinstance(custom_format, RemoteRepositoryFormat):
331
# We will use the custom format to create repositories over the
332
# wire; expose its details like rich_root_data for code to
334
result._custom_format = custom_format
337
def get_branch_format(self):
338
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
339
if not isinstance(result, RemoteBranchFormat):
340
new_result = RemoteBranchFormat()
341
new_result._custom_format = result
343
self.set_branch_format(new_result)
347
repository_format = property(__return_repository_format,
348
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
351
class RemoteControlStore(config.IniFileStore):
352
"""Control store which attempts to use HPSS calls to retrieve control store.
354
Note that this is specific to bzr-based formats.
357
def __init__(self, bzrdir):
358
super(RemoteControlStore, self).__init__()
360
self._real_store = None
362
def lock_write(self, token=None):
364
return self._real_store.lock_write(token)
368
return self._real_store.unlock()
372
# We need to be able to override the undecorated implementation
373
self.save_without_locking()
375
def save_without_locking(self):
376
super(RemoteControlStore, self).save()
378
def _ensure_real(self):
379
self.bzrdir._ensure_real()
380
if self._real_store is None:
381
self._real_store = config.ControlStore(self.bzrdir)
383
def external_url(self):
384
return self.bzrdir.user_url
386
def _load_content(self):
387
medium = self.bzrdir._client._medium
388
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
390
response, handler = self.bzrdir._call_expecting_body(
391
'BzrDir.get_config_file', path)
392
except errors.UnknownSmartMethod:
394
return self._real_store._load_content()
395
if len(response) and response[0] != 'ok':
396
raise errors.UnexpectedSmartServerResponse(response)
397
return handler.read_body_bytes()
399
def _save_content(self, content):
400
# FIXME JRV 2011-11-22: Ideally this should use a
401
# HPSS call too, but at the moment it is not possible
402
# to write lock control directories.
404
return self._real_store._save_content(content)
407
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
408
"""Control directory on a remote server, accessed via bzr:// or similar."""
410
def __init__(self, transport, format, _client=None, _force_probe=False):
411
"""Construct a RemoteBzrDir.
413
:param _client: Private parameter for testing. Disables probing and the
414
use of a real bzrdir.
416
_mod_bzrdir.BzrDir.__init__(self, transport, format)
417
# this object holds a delegated bzrdir that uses file-level operations
418
# to talk to the other side
419
self._real_bzrdir = None
420
self._has_working_tree = None
421
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
422
# create_branch for details.
423
self._next_open_branch_result = None
426
medium = transport.get_smart_medium()
427
self._client = client._SmartClient(medium)
429
self._client = _client
436
return '%s(%r)' % (self.__class__.__name__, self._client)
438
def _probe_bzrdir(self):
439
medium = self._client._medium
440
path = self._path_for_remote_call(self._client)
441
if medium._is_remote_before((2, 1)):
445
self._rpc_open_2_1(path)
447
except errors.UnknownSmartMethod:
448
medium._remember_remote_is_before((2, 1))
451
def _rpc_open_2_1(self, path):
452
response = self._call('BzrDir.open_2.1', path)
453
if response == ('no',):
454
raise errors.NotBranchError(path=self.root_transport.base)
455
elif response[0] == 'yes':
456
if response[1] == 'yes':
457
self._has_working_tree = True
458
elif response[1] == 'no':
459
self._has_working_tree = False
461
raise errors.UnexpectedSmartServerResponse(response)
463
raise errors.UnexpectedSmartServerResponse(response)
465
def _rpc_open(self, path):
466
response = self._call('BzrDir.open', path)
467
if response not in [('yes',), ('no',)]:
468
raise errors.UnexpectedSmartServerResponse(response)
469
if response == ('no',):
470
raise errors.NotBranchError(path=self.root_transport.base)
472
def _ensure_real(self):
473
"""Ensure that there is a _real_bzrdir set.
475
Used before calls to self._real_bzrdir.
477
if not self._real_bzrdir:
478
if 'hpssvfs' in debug.debug_flags:
480
warning('VFS BzrDir access triggered\n%s',
481
''.join(traceback.format_stack()))
482
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
483
self.root_transport, _server_formats=False)
484
self._format._network_name = \
485
self._real_bzrdir._format.network_name()
487
def _translate_error(self, err, **context):
488
_translate_error(err, bzrdir=self, **context)
490
def break_lock(self):
491
# Prevent aliasing problems in the next_open_branch_result cache.
492
# See create_branch for rationale.
493
self._next_open_branch_result = None
494
return _mod_bzrdir.BzrDir.break_lock(self)
496
def _vfs_checkout_metadir(self):
498
return self._real_bzrdir.checkout_metadir()
500
def checkout_metadir(self):
501
"""Retrieve the controldir format to use for checkouts of this one.
503
medium = self._client._medium
504
if medium._is_remote_before((2, 5)):
505
return self._vfs_checkout_metadir()
506
path = self._path_for_remote_call(self._client)
508
response = self._client.call('BzrDir.checkout_metadir',
510
except errors.UnknownSmartMethod:
511
medium._remember_remote_is_before((2, 5))
512
return self._vfs_checkout_metadir()
513
if len(response) != 3:
514
raise errors.UnexpectedSmartServerResponse(response)
515
control_name, repo_name, branch_name = response
517
format = controldir.network_format_registry.get(control_name)
519
raise errors.UnknownFormatError(kind='control',
523
repo_format = _mod_repository.network_format_registry.get(
526
raise errors.UnknownFormatError(kind='repository',
528
format.repository_format = repo_format
531
format.set_branch_format(
532
branch.network_format_registry.get(branch_name))
534
raise errors.UnknownFormatError(kind='branch',
538
def _vfs_cloning_metadir(self, require_stacking=False):
540
return self._real_bzrdir.cloning_metadir(
541
require_stacking=require_stacking)
543
def cloning_metadir(self, require_stacking=False):
544
medium = self._client._medium
545
if medium._is_remote_before((1, 13)):
546
return self._vfs_cloning_metadir(require_stacking=require_stacking)
547
verb = 'BzrDir.cloning_metadir'
552
path = self._path_for_remote_call(self._client)
554
response = self._call(verb, path, stacking)
555
except errors.UnknownSmartMethod:
556
medium._remember_remote_is_before((1, 13))
557
return self._vfs_cloning_metadir(require_stacking=require_stacking)
558
except errors.UnknownErrorFromSmartServer, err:
559
if err.error_tuple != ('BranchReference',):
561
# We need to resolve the branch reference to determine the
562
# cloning_metadir. This causes unnecessary RPCs to open the
563
# referenced branch (and bzrdir, etc) but only when the caller
564
# didn't already resolve the branch reference.
565
referenced_branch = self.open_branch()
566
return referenced_branch.bzrdir.cloning_metadir()
567
if len(response) != 3:
568
raise errors.UnexpectedSmartServerResponse(response)
569
control_name, repo_name, branch_info = response
570
if len(branch_info) != 2:
571
raise errors.UnexpectedSmartServerResponse(response)
572
branch_ref, branch_name = branch_info
574
format = controldir.network_format_registry.get(control_name)
576
raise errors.UnknownFormatError(kind='control', format=control_name)
580
format.repository_format = _mod_repository.network_format_registry.get(
583
raise errors.UnknownFormatError(kind='repository',
585
if branch_ref == 'ref':
586
# XXX: we need possible_transports here to avoid reopening the
587
# connection to the referenced location
588
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
589
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
590
format.set_branch_format(branch_format)
591
elif branch_ref == 'branch':
594
branch_format = branch.network_format_registry.get(
597
raise errors.UnknownFormatError(kind='branch',
599
format.set_branch_format(branch_format)
601
raise errors.UnexpectedSmartServerResponse(response)
604
def create_repository(self, shared=False):
605
# as per meta1 formats - just delegate to the format object which may
607
result = self._format.repository_format.initialize(self, shared)
608
if not isinstance(result, RemoteRepository):
609
return self.open_repository()
613
def destroy_repository(self):
614
"""See BzrDir.destroy_repository"""
615
path = self._path_for_remote_call(self._client)
617
response = self._call('BzrDir.destroy_repository', path)
618
except errors.UnknownSmartMethod:
620
self._real_bzrdir.destroy_repository()
622
if response[0] != 'ok':
623
raise SmartProtocolError('unexpected response code %s' % (response,))
625
def create_branch(self, name=None, repository=None,
626
append_revisions_only=None):
627
# as per meta1 formats - just delegate to the format object which may
629
real_branch = self._format.get_branch_format().initialize(self,
630
name=name, repository=repository,
631
append_revisions_only=append_revisions_only)
632
if not isinstance(real_branch, RemoteBranch):
633
if not isinstance(repository, RemoteRepository):
634
raise AssertionError(
635
'need a RemoteRepository to use with RemoteBranch, got %r'
637
result = RemoteBranch(self, repository, real_branch, name=name)
640
# BzrDir.clone_on_transport() uses the result of create_branch but does
641
# not return it to its callers; we save approximately 8% of our round
642
# trips by handing the branch we created back to the first caller to
643
# open_branch rather than probing anew. Long term we need a API in
644
# bzrdir that doesn't discard result objects (like result_branch).
646
self._next_open_branch_result = result
649
def destroy_branch(self, name=None):
650
"""See BzrDir.destroy_branch"""
651
path = self._path_for_remote_call(self._client)
657
response = self._call('BzrDir.destroy_branch', path, *args)
658
except errors.UnknownSmartMethod:
660
self._real_bzrdir.destroy_branch(name=name)
661
self._next_open_branch_result = None
663
self._next_open_branch_result = None
664
if response[0] != 'ok':
665
raise SmartProtocolError('unexpected response code %s' % (response,))
667
def create_workingtree(self, revision_id=None, from_branch=None,
668
accelerator_tree=None, hardlink=False):
669
raise errors.NotLocalUrl(self.transport.base)
671
def find_branch_format(self, name=None):
672
"""Find the branch 'format' for this bzrdir.
674
This might be a synthetic object for e.g. RemoteBranch and SVN.
676
b = self.open_branch(name=name)
679
def get_branch_reference(self, name=None):
680
"""See BzrDir.get_branch_reference()."""
682
# XXX JRV20100304: Support opening colocated branches
683
raise errors.NoColocatedBranchSupport(self)
684
response = self._get_branch_reference()
685
if response[0] == 'ref':
690
def _get_branch_reference(self):
691
path = self._path_for_remote_call(self._client)
692
medium = self._client._medium
694
('BzrDir.open_branchV3', (2, 1)),
695
('BzrDir.open_branchV2', (1, 13)),
696
('BzrDir.open_branch', None),
698
for verb, required_version in candidate_calls:
699
if required_version and medium._is_remote_before(required_version):
702
response = self._call(verb, path)
703
except errors.UnknownSmartMethod:
704
if required_version is None:
706
medium._remember_remote_is_before(required_version)
709
if verb == 'BzrDir.open_branch':
710
if response[0] != 'ok':
711
raise errors.UnexpectedSmartServerResponse(response)
712
if response[1] != '':
713
return ('ref', response[1])
715
return ('branch', '')
716
if response[0] not in ('ref', 'branch'):
717
raise errors.UnexpectedSmartServerResponse(response)
720
def _get_tree_branch(self, name=None):
721
"""See BzrDir._get_tree_branch()."""
722
return None, self.open_branch(name=name)
724
def open_branch(self, name=None, unsupported=False,
725
ignore_fallbacks=False, possible_transports=None):
727
raise NotImplementedError('unsupported flag support not implemented yet.')
728
if self._next_open_branch_result is not None:
729
# See create_branch for details.
730
result = self._next_open_branch_result
731
self._next_open_branch_result = None
733
response = self._get_branch_reference()
734
if response[0] == 'ref':
735
# a branch reference, use the existing BranchReference logic.
736
format = BranchReferenceFormat()
737
return format.open(self, name=name, _found=True,
738
location=response[1], ignore_fallbacks=ignore_fallbacks,
739
possible_transports=possible_transports)
740
branch_format_name = response[1]
741
if not branch_format_name:
742
branch_format_name = None
743
format = RemoteBranchFormat(network_name=branch_format_name)
744
return RemoteBranch(self, self.find_repository(), format=format,
745
setup_stacking=not ignore_fallbacks, name=name,
746
possible_transports=possible_transports)
748
def _open_repo_v1(self, path):
749
verb = 'BzrDir.find_repository'
750
response = self._call(verb, path)
751
if response[0] != 'ok':
752
raise errors.UnexpectedSmartServerResponse(response)
753
# servers that only support the v1 method don't support external
756
repo = self._real_bzrdir.open_repository()
757
response = response + ('no', repo._format.network_name())
758
return response, repo
760
def _open_repo_v2(self, path):
761
verb = 'BzrDir.find_repositoryV2'
762
response = self._call(verb, path)
763
if response[0] != 'ok':
764
raise errors.UnexpectedSmartServerResponse(response)
766
repo = self._real_bzrdir.open_repository()
767
response = response + (repo._format.network_name(),)
768
return response, repo
770
def _open_repo_v3(self, path):
771
verb = 'BzrDir.find_repositoryV3'
772
medium = self._client._medium
773
if medium._is_remote_before((1, 13)):
774
raise errors.UnknownSmartMethod(verb)
776
response = self._call(verb, path)
777
except errors.UnknownSmartMethod:
778
medium._remember_remote_is_before((1, 13))
780
if response[0] != 'ok':
781
raise errors.UnexpectedSmartServerResponse(response)
782
return response, None
784
def open_repository(self):
785
path = self._path_for_remote_call(self._client)
787
for probe in [self._open_repo_v3, self._open_repo_v2,
790
response, real_repo = probe(path)
792
except errors.UnknownSmartMethod:
795
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
796
if response[0] != 'ok':
797
raise errors.UnexpectedSmartServerResponse(response)
798
if len(response) != 6:
799
raise SmartProtocolError('incorrect response length %s' % (response,))
800
if response[1] == '':
801
# repo is at this dir.
802
format = response_tuple_to_repo_format(response[2:])
803
# Used to support creating a real format instance when needed.
804
format._creating_bzrdir = self
805
remote_repo = RemoteRepository(self, format)
806
format._creating_repo = remote_repo
807
if real_repo is not None:
808
remote_repo._set_real_repository(real_repo)
811
raise errors.NoRepositoryPresent(self)
813
def has_workingtree(self):
814
if self._has_working_tree is None:
815
path = self._path_for_remote_call(self._client)
817
response = self._call('BzrDir.has_workingtree', path)
818
except errors.UnknownSmartMethod:
820
self._has_working_tree = self._real_bzrdir.has_workingtree()
822
if response[0] not in ('yes', 'no'):
823
raise SmartProtocolError('unexpected response code %s' % (response,))
824
self._has_working_tree = (response[0] == 'yes')
825
return self._has_working_tree
827
def open_workingtree(self, recommend_upgrade=True):
828
if self.has_workingtree():
829
raise errors.NotLocalUrl(self.root_transport)
831
raise errors.NoWorkingTree(self.root_transport.base)
833
def _path_for_remote_call(self, client):
834
"""Return the path to be used for this bzrdir in a remote call."""
835
return urlutils.split_segment_parameters_raw(
836
client.remote_path_from_transport(self.root_transport))[0]
838
def get_branch_transport(self, branch_format, name=None):
840
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
842
def get_repository_transport(self, repository_format):
844
return self._real_bzrdir.get_repository_transport(repository_format)
846
def get_workingtree_transport(self, workingtree_format):
848
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
850
def can_convert_format(self):
851
"""Upgrading of remote bzrdirs is not supported yet."""
854
def needs_format_conversion(self, format):
855
"""Upgrading of remote bzrdirs is not supported yet."""
858
def _get_config(self):
859
return RemoteBzrDirConfig(self)
861
def _get_config_store(self):
862
return RemoteControlStore(self)
865
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
866
"""Format for repositories accessed over a _SmartClient.
868
Instances of this repository are represented by RemoteRepository
871
The RemoteRepositoryFormat is parameterized during construction
872
to reflect the capabilities of the real, remote format. Specifically
873
the attributes rich_root_data and supports_tree_reference are set
874
on a per instance basis, and are not set (and should not be) at
877
:ivar _custom_format: If set, a specific concrete repository format that
878
will be used when initializing a repository with this
879
RemoteRepositoryFormat.
880
:ivar _creating_repo: If set, the repository object that this
881
RemoteRepositoryFormat was created for: it can be called into
882
to obtain data like the network name.
885
_matchingbzrdir = RemoteBzrDirFormat()
886
supports_full_versioned_files = True
887
supports_leaving_lock = True
890
_mod_repository.RepositoryFormat.__init__(self)
891
self._custom_format = None
892
self._network_name = None
893
self._creating_bzrdir = None
894
self._revision_graph_can_have_wrong_parents = None
895
self._supports_chks = None
896
self._supports_external_lookups = None
897
self._supports_tree_reference = None
898
self._supports_funky_characters = None
899
self._supports_nesting_repositories = None
900
self._rich_root_data = None
903
return "%s(_network_name=%r)" % (self.__class__.__name__,
907
def fast_deltas(self):
909
return self._custom_format.fast_deltas
912
def rich_root_data(self):
913
if self._rich_root_data is None:
915
self._rich_root_data = self._custom_format.rich_root_data
916
return self._rich_root_data
919
def supports_chks(self):
920
if self._supports_chks is None:
922
self._supports_chks = self._custom_format.supports_chks
923
return self._supports_chks
926
def supports_external_lookups(self):
927
if self._supports_external_lookups is None:
929
self._supports_external_lookups = \
930
self._custom_format.supports_external_lookups
931
return self._supports_external_lookups
934
def supports_funky_characters(self):
935
if self._supports_funky_characters is None:
937
self._supports_funky_characters = \
938
self._custom_format.supports_funky_characters
939
return self._supports_funky_characters
942
def supports_nesting_repositories(self):
943
if self._supports_nesting_repositories is None:
945
self._supports_nesting_repositories = \
946
self._custom_format.supports_nesting_repositories
947
return self._supports_nesting_repositories
950
def supports_tree_reference(self):
951
if self._supports_tree_reference is None:
953
self._supports_tree_reference = \
954
self._custom_format.supports_tree_reference
955
return self._supports_tree_reference
958
def revision_graph_can_have_wrong_parents(self):
959
if self._revision_graph_can_have_wrong_parents is None:
961
self._revision_graph_can_have_wrong_parents = \
962
self._custom_format.revision_graph_can_have_wrong_parents
963
return self._revision_graph_can_have_wrong_parents
965
def _vfs_initialize(self, a_bzrdir, shared):
966
"""Helper for common code in initialize."""
967
if self._custom_format:
968
# Custom format requested
969
result = self._custom_format.initialize(a_bzrdir, shared=shared)
970
elif self._creating_bzrdir is not None:
971
# Use the format that the repository we were created to back
973
prior_repo = self._creating_bzrdir.open_repository()
974
prior_repo._ensure_real()
975
result = prior_repo._real_repository._format.initialize(
976
a_bzrdir, shared=shared)
978
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
979
# support remote initialization.
980
# We delegate to a real object at this point (as RemoteBzrDir
981
# delegate to the repository format which would lead to infinite
982
# recursion if we just called a_bzrdir.create_repository.
983
a_bzrdir._ensure_real()
984
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
985
if not isinstance(result, RemoteRepository):
986
return self.open(a_bzrdir)
990
def initialize(self, a_bzrdir, shared=False):
991
# Being asked to create on a non RemoteBzrDir:
992
if not isinstance(a_bzrdir, RemoteBzrDir):
993
return self._vfs_initialize(a_bzrdir, shared)
994
medium = a_bzrdir._client._medium
995
if medium._is_remote_before((1, 13)):
996
return self._vfs_initialize(a_bzrdir, shared)
997
# Creating on a remote bzr dir.
998
# 1) get the network name to use.
999
if self._custom_format:
1000
network_name = self._custom_format.network_name()
1001
elif self._network_name:
1002
network_name = self._network_name
1004
# Select the current bzrlib default and ask for that.
1005
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1006
reference_format = reference_bzrdir_format.repository_format
1007
network_name = reference_format.network_name()
1008
# 2) try direct creation via RPC
1009
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1010
verb = 'BzrDir.create_repository'
1014
shared_str = 'False'
1016
response = a_bzrdir._call(verb, path, network_name, shared_str)
1017
except errors.UnknownSmartMethod:
1018
# Fallback - use vfs methods
1019
medium._remember_remote_is_before((1, 13))
1020
return self._vfs_initialize(a_bzrdir, shared)
1022
# Turn the response into a RemoteRepository object.
1023
format = response_tuple_to_repo_format(response[1:])
1024
# Used to support creating a real format instance when needed.
1025
format._creating_bzrdir = a_bzrdir
1026
remote_repo = RemoteRepository(a_bzrdir, format)
1027
format._creating_repo = remote_repo
1030
def open(self, a_bzrdir):
1031
if not isinstance(a_bzrdir, RemoteBzrDir):
1032
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1033
return a_bzrdir.open_repository()
1035
def _ensure_real(self):
1036
if self._custom_format is None:
1038
self._custom_format = _mod_repository.network_format_registry.get(
1041
raise errors.UnknownFormatError(kind='repository',
1042
format=self._network_name)
1045
def _fetch_order(self):
1047
return self._custom_format._fetch_order
1050
def _fetch_uses_deltas(self):
1052
return self._custom_format._fetch_uses_deltas
1055
def _fetch_reconcile(self):
1057
return self._custom_format._fetch_reconcile
1059
def get_format_description(self):
1061
return 'Remote: ' + self._custom_format.get_format_description()
1063
def __eq__(self, other):
1064
return self.__class__ is other.__class__
1066
def network_name(self):
1067
if self._network_name:
1068
return self._network_name
1069
self._creating_repo._ensure_real()
1070
return self._creating_repo._real_repository._format.network_name()
1073
def pack_compresses(self):
1075
return self._custom_format.pack_compresses
1078
def _serializer(self):
1080
return self._custom_format._serializer
1083
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1084
lock._RelockDebugMixin):
1085
"""Repository accessed over rpc.
1087
For the moment most operations are performed using local transport-backed
1091
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1092
"""Create a RemoteRepository instance.
1094
:param remote_bzrdir: The bzrdir hosting this repository.
1095
:param format: The RemoteFormat object to use.
1096
:param real_repository: If not None, a local implementation of the
1097
repository logic for the repository, usually accessing the data
1099
:param _client: Private testing parameter - override the smart client
1100
to be used by the repository.
1103
self._real_repository = real_repository
1105
self._real_repository = None
1106
self.bzrdir = remote_bzrdir
1108
self._client = remote_bzrdir._client
1110
self._client = _client
1111
self._format = format
1112
self._lock_mode = None
1113
self._lock_token = None
1114
self._write_group_tokens = None
1115
self._lock_count = 0
1116
self._leave_lock = False
1117
# Cache of revision parents; misses are cached during read locks, and
1118
# write locks when no _real_repository has been set.
1119
self._unstacked_provider = graph.CachingParentsProvider(
1120
get_parent_map=self._get_parent_map_rpc)
1121
self._unstacked_provider.disable_cache()
1123
# These depend on the actual remote format, so force them off for
1124
# maximum compatibility. XXX: In future these should depend on the
1125
# remote repository instance, but this is irrelevant until we perform
1126
# reconcile via an RPC call.
1127
self._reconcile_does_inventory_gc = False
1128
self._reconcile_fixes_text_parents = False
1129
self._reconcile_backsup_inventory = False
1130
self.base = self.bzrdir.transport.base
1131
# Additional places to query for data.
1132
self._fallback_repositories = []
1135
def user_transport(self):
1136
return self.bzrdir.user_transport
1139
def control_transport(self):
1140
# XXX: Normally you shouldn't directly get at the remote repository
1141
# transport, but I'm not sure it's worth making this method
1142
# optional -- mbp 2010-04-21
1143
return self.bzrdir.get_repository_transport(None)
1146
return "%s(%s)" % (self.__class__.__name__, self.base)
1150
def abort_write_group(self, suppress_errors=False):
1151
"""Complete a write group on the decorated repository.
1153
Smart methods perform operations in a single step so this API
1154
is not really applicable except as a compatibility thunk
1155
for older plugins that don't use e.g. the CommitBuilder
1158
:param suppress_errors: see Repository.abort_write_group.
1160
if self._real_repository:
1162
return self._real_repository.abort_write_group(
1163
suppress_errors=suppress_errors)
1164
if not self.is_in_write_group():
1166
mutter('(suppressed) not in write group')
1168
raise errors.BzrError("not in write group")
1169
path = self.bzrdir._path_for_remote_call(self._client)
1171
response = self._call('Repository.abort_write_group', path,
1172
self._lock_token, self._write_group_tokens)
1173
except Exception, exc:
1174
self._write_group = None
1175
if not suppress_errors:
1177
mutter('abort_write_group failed')
1178
log_exception_quietly()
1179
note(gettext('bzr: ERROR (ignored): %s'), exc)
1181
if response != ('ok', ):
1182
raise errors.UnexpectedSmartServerResponse(response)
1183
self._write_group_tokens = None
1186
def chk_bytes(self):
1187
"""Decorate the real repository for now.
1189
In the long term a full blown network facility is needed to avoid
1190
creating a real repository object locally.
1193
return self._real_repository.chk_bytes
1195
def commit_write_group(self):
1196
"""Complete a write group on the decorated repository.
1198
Smart methods perform operations in a single step so this API
1199
is not really applicable except as a compatibility thunk
1200
for older plugins that don't use e.g. the CommitBuilder
1203
if self._real_repository:
1205
return self._real_repository.commit_write_group()
1206
if not self.is_in_write_group():
1207
raise errors.BzrError("not in write group")
1208
path = self.bzrdir._path_for_remote_call(self._client)
1209
response = self._call('Repository.commit_write_group', path,
1210
self._lock_token, self._write_group_tokens)
1211
if response != ('ok', ):
1212
raise errors.UnexpectedSmartServerResponse(response)
1213
self._write_group_tokens = None
1214
# Refresh data after writing to the repository.
1217
def resume_write_group(self, tokens):
1218
if self._real_repository:
1219
return self._real_repository.resume_write_group(tokens)
1220
path = self.bzrdir._path_for_remote_call(self._client)
1222
response = self._call('Repository.check_write_group', path,
1223
self._lock_token, tokens)
1224
except errors.UnknownSmartMethod:
1226
return self._real_repository.resume_write_group(tokens)
1227
if response != ('ok', ):
1228
raise errors.UnexpectedSmartServerResponse(response)
1229
self._write_group_tokens = tokens
1231
def suspend_write_group(self):
1232
if self._real_repository:
1233
return self._real_repository.suspend_write_group()
1234
ret = self._write_group_tokens or []
1235
self._write_group_tokens = None
1238
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1240
return self._real_repository.get_missing_parent_inventories(
1241
check_for_missing_texts=check_for_missing_texts)
1243
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1245
return self._real_repository.get_rev_id_for_revno(
1248
def get_rev_id_for_revno(self, revno, known_pair):
1249
"""See Repository.get_rev_id_for_revno."""
1250
path = self.bzrdir._path_for_remote_call(self._client)
1252
if self._client._medium._is_remote_before((1, 17)):
1253
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1254
response = self._call(
1255
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1256
except errors.UnknownSmartMethod:
1257
self._client._medium._remember_remote_is_before((1, 17))
1258
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1259
if response[0] == 'ok':
1260
return True, response[1]
1261
elif response[0] == 'history-incomplete':
1262
known_pair = response[1:3]
1263
for fallback in self._fallback_repositories:
1264
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1269
# Not found in any fallbacks
1270
return False, known_pair
1272
raise errors.UnexpectedSmartServerResponse(response)
1274
def _ensure_real(self):
1275
"""Ensure that there is a _real_repository set.
1277
Used before calls to self._real_repository.
1279
Note that _ensure_real causes many roundtrips to the server which are
1280
not desirable, and prevents the use of smart one-roundtrip RPC's to
1281
perform complex operations (such as accessing parent data, streaming
1282
revisions etc). Adding calls to _ensure_real should only be done when
1283
bringing up new functionality, adding fallbacks for smart methods that
1284
require a fallback path, and never to replace an existing smart method
1285
invocation. If in doubt chat to the bzr network team.
1287
if self._real_repository is None:
1288
if 'hpssvfs' in debug.debug_flags:
1290
warning('VFS Repository access triggered\n%s',
1291
''.join(traceback.format_stack()))
1292
self._unstacked_provider.missing_keys.clear()
1293
self.bzrdir._ensure_real()
1294
self._set_real_repository(
1295
self.bzrdir._real_bzrdir.open_repository())
1297
def _translate_error(self, err, **context):
1298
self.bzrdir._translate_error(err, repository=self, **context)
1300
def find_text_key_references(self):
1301
"""Find the text key references within the repository.
1303
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1304
to whether they were referred to by the inventory of the
1305
revision_id that they contain. The inventory texts from all present
1306
revision ids are assessed to generate this report.
1309
return self._real_repository.find_text_key_references()
1311
def _generate_text_key_index(self):
1312
"""Generate a new text key index for the repository.
1314
This is an expensive function that will take considerable time to run.
1316
:return: A dict mapping (file_id, revision_id) tuples to a list of
1317
parents, also (file_id, revision_id) tuples.
1320
return self._real_repository._generate_text_key_index()
1322
def _get_revision_graph(self, revision_id):
1323
"""Private method for using with old (< 1.2) servers to fallback."""
1324
if revision_id is None:
1326
elif _mod_revision.is_null(revision_id):
1329
path = self.bzrdir._path_for_remote_call(self._client)
1330
response = self._call_expecting_body(
1331
'Repository.get_revision_graph', path, revision_id)
1332
response_tuple, response_handler = response
1333
if response_tuple[0] != 'ok':
1334
raise errors.UnexpectedSmartServerResponse(response_tuple)
1335
coded = response_handler.read_body_bytes()
1337
# no revisions in this repository!
1339
lines = coded.split('\n')
1342
d = tuple(line.split())
1343
revision_graph[d[0]] = d[1:]
1345
return revision_graph
1347
def _get_sink(self):
1348
"""See Repository._get_sink()."""
1349
return RemoteStreamSink(self)
1351
def _get_source(self, to_format):
1352
"""Return a source for streaming from this repository."""
1353
return RemoteStreamSource(self, to_format)
1356
def get_file_graph(self):
1357
return graph.Graph(self.texts)
1360
def has_revision(self, revision_id):
1361
"""True if this repository has a copy of the revision."""
1362
# Copy of bzrlib.repository.Repository.has_revision
1363
return revision_id in self.has_revisions((revision_id,))
1366
def has_revisions(self, revision_ids):
1367
"""Probe to find out the presence of multiple revisions.
1369
:param revision_ids: An iterable of revision_ids.
1370
:return: A set of the revision_ids that were present.
1372
# Copy of bzrlib.repository.Repository.has_revisions
1373
parent_map = self.get_parent_map(revision_ids)
1374
result = set(parent_map)
1375
if _mod_revision.NULL_REVISION in revision_ids:
1376
result.add(_mod_revision.NULL_REVISION)
1379
def _has_same_fallbacks(self, other_repo):
1380
"""Returns true if the repositories have the same fallbacks."""
1381
# XXX: copied from Repository; it should be unified into a base class
1382
# <https://bugs.launchpad.net/bzr/+bug/401622>
1383
my_fb = self._fallback_repositories
1384
other_fb = other_repo._fallback_repositories
1385
if len(my_fb) != len(other_fb):
1387
for f, g in zip(my_fb, other_fb):
1388
if not f.has_same_location(g):
1392
def has_same_location(self, other):
1393
# TODO: Move to RepositoryBase and unify with the regular Repository
1394
# one; unfortunately the tests rely on slightly different behaviour at
1395
# present -- mbp 20090710
1396
return (self.__class__ is other.__class__ and
1397
self.bzrdir.transport.base == other.bzrdir.transport.base)
1399
def get_graph(self, other_repository=None):
1400
"""Return the graph for this repository format"""
1401
parents_provider = self._make_parents_provider(other_repository)
1402
return graph.Graph(parents_provider)
1405
def get_known_graph_ancestry(self, revision_ids):
1406
"""Return the known graph for a set of revision ids and their ancestors.
1408
st = static_tuple.StaticTuple
1409
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1410
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1411
return graph.GraphThunkIdsToKeys(known_graph)
1413
def gather_stats(self, revid=None, committers=None):
1414
"""See Repository.gather_stats()."""
1415
path = self.bzrdir._path_for_remote_call(self._client)
1416
# revid can be None to indicate no revisions, not just NULL_REVISION
1417
if revid is None or _mod_revision.is_null(revid):
1421
if committers is None or not committers:
1422
fmt_committers = 'no'
1424
fmt_committers = 'yes'
1425
response_tuple, response_handler = self._call_expecting_body(
1426
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1427
if response_tuple[0] != 'ok':
1428
raise errors.UnexpectedSmartServerResponse(response_tuple)
1430
body = response_handler.read_body_bytes()
1432
for line in body.split('\n'):
1435
key, val_text = line.split(':')
1436
if key in ('revisions', 'size', 'committers'):
1437
result[key] = int(val_text)
1438
elif key in ('firstrev', 'latestrev'):
1439
values = val_text.split(' ')[1:]
1440
result[key] = (float(values[0]), long(values[1]))
1444
def find_branches(self, using=False):
1445
"""See Repository.find_branches()."""
1446
# should be an API call to the server.
1448
return self._real_repository.find_branches(using=using)
1450
def get_physical_lock_status(self):
1451
"""See Repository.get_physical_lock_status()."""
1452
path = self.bzrdir._path_for_remote_call(self._client)
1454
response = self._call('Repository.get_physical_lock_status', path)
1455
except errors.UnknownSmartMethod:
1457
return self._real_repository.get_physical_lock_status()
1458
if response[0] not in ('yes', 'no'):
1459
raise errors.UnexpectedSmartServerResponse(response)
1460
return (response[0] == 'yes')
1462
def is_in_write_group(self):
1463
"""Return True if there is an open write group.
1465
write groups are only applicable locally for the smart server..
1467
if self._write_group_tokens is not None:
1469
if self._real_repository:
1470
return self._real_repository.is_in_write_group()
1472
def is_locked(self):
1473
return self._lock_count >= 1
1475
def is_shared(self):
1476
"""See Repository.is_shared()."""
1477
path = self.bzrdir._path_for_remote_call(self._client)
1478
response = self._call('Repository.is_shared', path)
1479
if response[0] not in ('yes', 'no'):
1480
raise SmartProtocolError('unexpected response code %s' % (response,))
1481
return response[0] == 'yes'
1483
def is_write_locked(self):
1484
return self._lock_mode == 'w'
1486
def _warn_if_deprecated(self, branch=None):
1487
# If we have a real repository, the check will be done there, if we
1488
# don't the check will be done remotely.
1491
def lock_read(self):
1492
"""Lock the repository for read operations.
1494
:return: A bzrlib.lock.LogicalLockResult.
1496
# wrong eventually - want a local lock cache context
1497
if not self._lock_mode:
1498
self._note_lock('r')
1499
self._lock_mode = 'r'
1500
self._lock_count = 1
1501
self._unstacked_provider.enable_cache(cache_misses=True)
1502
if self._real_repository is not None:
1503
self._real_repository.lock_read()
1504
for repo in self._fallback_repositories:
1507
self._lock_count += 1
1508
return lock.LogicalLockResult(self.unlock)
1510
def _remote_lock_write(self, token):
1511
path = self.bzrdir._path_for_remote_call(self._client)
1514
err_context = {'token': token}
1515
response = self._call('Repository.lock_write', path, token,
1517
if response[0] == 'ok':
1518
ok, token = response
1521
raise errors.UnexpectedSmartServerResponse(response)
1523
def lock_write(self, token=None, _skip_rpc=False):
1524
if not self._lock_mode:
1525
self._note_lock('w')
1527
if self._lock_token is not None:
1528
if token != self._lock_token:
1529
raise errors.TokenMismatch(token, self._lock_token)
1530
self._lock_token = token
1532
self._lock_token = self._remote_lock_write(token)
1533
# if self._lock_token is None, then this is something like packs or
1534
# svn where we don't get to lock the repo, or a weave style repository
1535
# where we cannot lock it over the wire and attempts to do so will
1537
if self._real_repository is not None:
1538
self._real_repository.lock_write(token=self._lock_token)
1539
if token is not None:
1540
self._leave_lock = True
1542
self._leave_lock = False
1543
self._lock_mode = 'w'
1544
self._lock_count = 1
1545
cache_misses = self._real_repository is None
1546
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1547
for repo in self._fallback_repositories:
1548
# Writes don't affect fallback repos
1550
elif self._lock_mode == 'r':
1551
raise errors.ReadOnlyError(self)
1553
self._lock_count += 1
1554
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1556
def leave_lock_in_place(self):
1557
if not self._lock_token:
1558
raise NotImplementedError(self.leave_lock_in_place)
1559
self._leave_lock = True
1561
def dont_leave_lock_in_place(self):
1562
if not self._lock_token:
1563
raise NotImplementedError(self.dont_leave_lock_in_place)
1564
self._leave_lock = False
1566
def _set_real_repository(self, repository):
1567
"""Set the _real_repository for this repository.
1569
:param repository: The repository to fallback to for non-hpss
1570
implemented operations.
1572
if self._real_repository is not None:
1573
# Replacing an already set real repository.
1574
# We cannot do this [currently] if the repository is locked -
1575
# synchronised state might be lost.
1576
if self.is_locked():
1577
raise AssertionError('_real_repository is already set')
1578
if isinstance(repository, RemoteRepository):
1579
raise AssertionError()
1580
self._real_repository = repository
1581
# three code paths happen here:
1582
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1583
# up stacking. In this case self._fallback_repositories is [], and the
1584
# real repo is already setup. Preserve the real repo and
1585
# RemoteRepository.add_fallback_repository will avoid adding
1587
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1588
# ensure_real is triggered from a branch, the real repository to
1589
# set already has a matching list with separate instances, but
1590
# as they are also RemoteRepositories we don't worry about making the
1591
# lists be identical.
1592
# 3) new servers, RemoteRepository.ensure_real is triggered before
1593
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1594
# and need to populate it.
1595
if (self._fallback_repositories and
1596
len(self._real_repository._fallback_repositories) !=
1597
len(self._fallback_repositories)):
1598
if len(self._real_repository._fallback_repositories):
1599
raise AssertionError(
1600
"cannot cleanly remove existing _fallback_repositories")
1601
for fb in self._fallback_repositories:
1602
self._real_repository.add_fallback_repository(fb)
1603
if self._lock_mode == 'w':
1604
# if we are already locked, the real repository must be able to
1605
# acquire the lock with our token.
1606
self._real_repository.lock_write(self._lock_token)
1607
elif self._lock_mode == 'r':
1608
self._real_repository.lock_read()
1609
if self._write_group_tokens is not None:
1610
# if we are already in a write group, resume it
1611
self._real_repository.resume_write_group(self._write_group_tokens)
1612
self._write_group_tokens = None
1614
def start_write_group(self):
1615
"""Start a write group on the decorated repository.
1617
Smart methods perform operations in a single step so this API
1618
is not really applicable except as a compatibility thunk
1619
for older plugins that don't use e.g. the CommitBuilder
1622
if self._real_repository:
1624
return self._real_repository.start_write_group()
1625
if not self.is_write_locked():
1626
raise errors.NotWriteLocked(self)
1627
if self._write_group_tokens is not None:
1628
raise errors.BzrError('already in a write group')
1629
path = self.bzrdir._path_for_remote_call(self._client)
1631
response = self._call('Repository.start_write_group', path,
1633
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1635
return self._real_repository.start_write_group()
1636
if response[0] != 'ok':
1637
raise errors.UnexpectedSmartServerResponse(response)
1638
self._write_group_tokens = response[1]
1640
def _unlock(self, token):
1641
path = self.bzrdir._path_for_remote_call(self._client)
1643
# with no token the remote repository is not persistently locked.
1645
err_context = {'token': token}
1646
response = self._call('Repository.unlock', path, token,
1648
if response == ('ok',):
1651
raise errors.UnexpectedSmartServerResponse(response)
1653
@only_raises(errors.LockNotHeld, errors.LockBroken)
1655
if not self._lock_count:
1656
return lock.cant_unlock_not_held(self)
1657
self._lock_count -= 1
1658
if self._lock_count > 0:
1660
self._unstacked_provider.disable_cache()
1661
old_mode = self._lock_mode
1662
self._lock_mode = None
1664
# The real repository is responsible at present for raising an
1665
# exception if it's in an unfinished write group. However, it
1666
# normally will *not* actually remove the lock from disk - that's
1667
# done by the server on receiving the Repository.unlock call.
1668
# This is just to let the _real_repository stay up to date.
1669
if self._real_repository is not None:
1670
self._real_repository.unlock()
1671
elif self._write_group_tokens is not None:
1672
self.abort_write_group()
1674
# The rpc-level lock should be released even if there was a
1675
# problem releasing the vfs-based lock.
1677
# Only write-locked repositories need to make a remote method
1678
# call to perform the unlock.
1679
old_token = self._lock_token
1680
self._lock_token = None
1681
if not self._leave_lock:
1682
self._unlock(old_token)
1683
# Fallbacks are always 'lock_read()' so we don't pay attention to
1685
for repo in self._fallback_repositories:
1688
def break_lock(self):
1689
# should hand off to the network
1690
path = self.bzrdir._path_for_remote_call(self._client)
1692
response = self._call("Repository.break_lock", path)
1693
except errors.UnknownSmartMethod:
1695
return self._real_repository.break_lock()
1696
if response != ('ok',):
1697
raise errors.UnexpectedSmartServerResponse(response)
1699
def _get_tarball(self, compression):
1700
"""Return a TemporaryFile containing a repository tarball.
1702
Returns None if the server does not support sending tarballs.
1705
path = self.bzrdir._path_for_remote_call(self._client)
1707
response, protocol = self._call_expecting_body(
1708
'Repository.tarball', path, compression)
1709
except errors.UnknownSmartMethod:
1710
protocol.cancel_read_body()
1712
if response[0] == 'ok':
1713
# Extract the tarball and return it
1714
t = tempfile.NamedTemporaryFile()
1715
# TODO: rpc layer should read directly into it...
1716
t.write(protocol.read_body_bytes())
1719
raise errors.UnexpectedSmartServerResponse(response)
1722
def sprout(self, to_bzrdir, revision_id=None):
1723
"""Create a descendent repository for new development.
1725
Unlike clone, this does not copy the settings of the repository.
1727
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1728
dest_repo.fetch(self, revision_id=revision_id)
1731
def _create_sprouting_repo(self, a_bzrdir, shared):
1732
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1733
# use target default format.
1734
dest_repo = a_bzrdir.create_repository()
1736
# Most control formats need the repository to be specifically
1737
# created, but on some old all-in-one formats it's not needed
1739
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1740
except errors.UninitializableFormat:
1741
dest_repo = a_bzrdir.open_repository()
1744
### These methods are just thin shims to the VFS object for now.
1747
def revision_tree(self, revision_id):
1748
revision_id = _mod_revision.ensure_null(revision_id)
1749
if revision_id == _mod_revision.NULL_REVISION:
1750
return InventoryRevisionTree(self,
1751
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1753
return list(self.revision_trees([revision_id]))[0]
1755
def get_serializer_format(self):
1756
path = self.bzrdir._path_for_remote_call(self._client)
1758
response = self._call('VersionedFileRepository.get_serializer_format',
1760
except errors.UnknownSmartMethod:
1762
return self._real_repository.get_serializer_format()
1763
if response[0] != 'ok':
1764
raise errors.UnexpectedSmartServerResponse(response)
1767
def get_commit_builder(self, branch, parents, config, timestamp=None,
1768
timezone=None, committer=None, revprops=None,
1769
revision_id=None, lossy=False):
1770
"""Obtain a CommitBuilder for this repository.
1772
:param branch: Branch to commit to.
1773
:param parents: Revision ids of the parents of the new revision.
1774
:param config: Configuration to use.
1775
:param timestamp: Optional timestamp recorded for commit.
1776
:param timezone: Optional timezone for timestamp.
1777
:param committer: Optional committer to set for commit.
1778
:param revprops: Optional dictionary of revision properties.
1779
:param revision_id: Optional revision id.
1780
:param lossy: Whether to discard data that can not be natively
1781
represented, when pushing to a foreign VCS
1783
if self._fallback_repositories and not self._format.supports_chks:
1784
raise errors.BzrError("Cannot commit directly to a stacked branch"
1785
" in pre-2a formats. See "
1786
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1787
if self._format.rich_root_data:
1788
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1790
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1791
result = commit_builder_kls(self, parents, config,
1792
timestamp, timezone, committer, revprops, revision_id,
1794
self.start_write_group()
1797
def add_fallback_repository(self, repository):
1798
"""Add a repository to use for looking up data not held locally.
1800
:param repository: A repository.
1802
if not self._format.supports_external_lookups:
1803
raise errors.UnstackableRepositoryFormat(
1804
self._format.network_name(), self.base)
1805
# We need to accumulate additional repositories here, to pass them in
1808
# Make the check before we lock: this raises an exception.
1809
self._check_fallback_repository(repository)
1810
if self.is_locked():
1811
# We will call fallback.unlock() when we transition to the unlocked
1812
# state, so always add a lock here. If a caller passes us a locked
1813
# repository, they are responsible for unlocking it later.
1814
repository.lock_read()
1815
self._fallback_repositories.append(repository)
1816
# If self._real_repository was parameterised already (e.g. because a
1817
# _real_branch had its get_stacked_on_url method called), then the
1818
# repository to be added may already be in the _real_repositories list.
1819
if self._real_repository is not None:
1820
fallback_locations = [repo.user_url for repo in
1821
self._real_repository._fallback_repositories]
1822
if repository.user_url not in fallback_locations:
1823
self._real_repository.add_fallback_repository(repository)
1825
def _check_fallback_repository(self, repository):
1826
"""Check that this repository can fallback to repository safely.
1828
Raise an error if not.
1830
:param repository: A repository to fallback to.
1832
return _mod_repository.InterRepository._assert_same_model(
1835
def add_inventory(self, revid, inv, parents):
1837
return self._real_repository.add_inventory(revid, inv, parents)
1839
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1840
parents, basis_inv=None, propagate_caches=False):
1842
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1843
delta, new_revision_id, parents, basis_inv=basis_inv,
1844
propagate_caches=propagate_caches)
1846
def add_revision(self, rev_id, rev, inv=None, config=None):
1848
return self._real_repository.add_revision(
1849
rev_id, rev, inv=inv, config=config)
1852
def get_inventory(self, revision_id):
1853
return list(self.iter_inventories([revision_id]))[0]
1855
def _iter_inventories_rpc(self, revision_ids, ordering):
1856
if ordering is None:
1857
ordering = 'unordered'
1858
path = self.bzrdir._path_for_remote_call(self._client)
1859
body = "\n".join(revision_ids)
1860
response_tuple, response_handler = (
1861
self._call_with_body_bytes_expecting_body(
1862
"VersionedFileRepository.get_inventories",
1863
(path, ordering), body))
1864
if response_tuple[0] != "ok":
1865
raise errors.UnexpectedSmartServerResponse(response_tuple)
1866
deserializer = inventory_delta.InventoryDeltaDeserializer()
1867
byte_stream = response_handler.read_streamed_body()
1868
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1870
# no results whatsoever
1872
src_format, stream = decoded
1873
if src_format.network_name() != self._format.network_name():
1874
raise AssertionError(
1875
"Mismatched RemoteRepository and stream src %r, %r" % (
1876
src_format.network_name(), self._format.network_name()))
1877
# ignore the src format, it's not really relevant
1878
prev_inv = Inventory(root_id=None,
1879
revision_id=_mod_revision.NULL_REVISION)
1880
# there should be just one substream, with inventory deltas
1881
substream_kind, substream = stream.next()
1882
if substream_kind != "inventory-deltas":
1883
raise AssertionError(
1884
"Unexpected stream %r received" % substream_kind)
1885
for record in substream:
1886
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1887
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1888
if parent_id != prev_inv.revision_id:
1889
raise AssertionError("invalid base %r != %r" % (parent_id,
1890
prev_inv.revision_id))
1891
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1892
yield inv, inv.revision_id
1895
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1897
return self._real_repository._iter_inventories(revision_ids, ordering)
1899
def iter_inventories(self, revision_ids, ordering=None):
1900
"""Get many inventories by revision_ids.
1902
This will buffer some or all of the texts used in constructing the
1903
inventories in memory, but will only parse a single inventory at a
1906
:param revision_ids: The expected revision ids of the inventories.
1907
:param ordering: optional ordering, e.g. 'topological'. If not
1908
specified, the order of revision_ids will be preserved (by
1909
buffering if necessary).
1910
:return: An iterator of inventories.
1912
if ((None in revision_ids)
1913
or (_mod_revision.NULL_REVISION in revision_ids)):
1914
raise ValueError('cannot get null revision inventory')
1915
for inv, revid in self._iter_inventories(revision_ids, ordering):
1917
raise errors.NoSuchRevision(self, revid)
1920
def _iter_inventories(self, revision_ids, ordering=None):
1921
if len(revision_ids) == 0:
1923
missing = set(revision_ids)
1924
if ordering is None:
1925
order_as_requested = True
1927
order = list(revision_ids)
1929
next_revid = order.pop()
1931
order_as_requested = False
1932
if ordering != 'unordered' and self._fallback_repositories:
1933
raise ValueError('unsupported ordering %r' % ordering)
1934
iter_inv_fns = [self._iter_inventories_rpc] + [
1935
fallback._iter_inventories for fallback in
1936
self._fallback_repositories]
1938
for iter_inv in iter_inv_fns:
1939
request = [revid for revid in revision_ids if revid in missing]
1940
for inv, revid in iter_inv(request, ordering):
1943
missing.remove(inv.revision_id)
1944
if ordering != 'unordered':
1948
if order_as_requested:
1949
# Yield as many results as we can while preserving order.
1950
while next_revid in invs:
1951
inv = invs.pop(next_revid)
1952
yield inv, inv.revision_id
1954
next_revid = order.pop()
1956
# We still want to fully consume the stream, just
1957
# in case it is not actually finished at this point
1960
except errors.UnknownSmartMethod:
1961
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
1965
if order_as_requested:
1966
if next_revid is not None:
1967
yield None, next_revid
1970
yield invs.get(revid), revid
1973
yield None, missing.pop()
1976
def get_revision(self, revision_id):
1977
return self.get_revisions([revision_id])[0]
1979
def get_transaction(self):
1981
return self._real_repository.get_transaction()
1984
def clone(self, a_bzrdir, revision_id=None):
1985
dest_repo = self._create_sprouting_repo(
1986
a_bzrdir, shared=self.is_shared())
1987
self.copy_content_into(dest_repo, revision_id)
1990
def make_working_trees(self):
1991
"""See Repository.make_working_trees"""
1992
path = self.bzrdir._path_for_remote_call(self._client)
1994
response = self._call('Repository.make_working_trees', path)
1995
except errors.UnknownSmartMethod:
1997
return self._real_repository.make_working_trees()
1998
if response[0] not in ('yes', 'no'):
1999
raise SmartProtocolError('unexpected response code %s' % (response,))
2000
return response[0] == 'yes'
2002
def refresh_data(self):
2003
"""Re-read any data needed to synchronise with disk.
2005
This method is intended to be called after another repository instance
2006
(such as one used by a smart server) has inserted data into the
2007
repository. On all repositories this will work outside of write groups.
2008
Some repository formats (pack and newer for bzrlib native formats)
2009
support refresh_data inside write groups. If called inside a write
2010
group on a repository that does not support refreshing in a write group
2011
IsInWriteGroupError will be raised.
2013
if self._real_repository is not None:
2014
self._real_repository.refresh_data()
2015
# Refresh the parents cache for this object
2016
self._unstacked_provider.disable_cache()
2017
self._unstacked_provider.enable_cache()
2019
def revision_ids_to_search_result(self, result_set):
2020
"""Convert a set of revision ids to a graph SearchResult."""
2021
result_parents = set()
2022
for parents in self.get_graph().get_parent_map(
2023
result_set).itervalues():
2024
result_parents.update(parents)
2025
included_keys = result_set.intersection(result_parents)
2026
start_keys = result_set.difference(included_keys)
2027
exclude_keys = result_parents.difference(result_set)
2028
result = vf_search.SearchResult(start_keys, exclude_keys,
2029
len(result_set), result_set)
2033
def search_missing_revision_ids(self, other,
2034
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2035
find_ghosts=True, revision_ids=None, if_present_ids=None,
2037
"""Return the revision ids that other has that this does not.
2039
These are returned in topological order.
2041
revision_id: only return revision ids included by revision_id.
2043
if symbol_versioning.deprecated_passed(revision_id):
2044
symbol_versioning.warn(
2045
'search_missing_revision_ids(revision_id=...) was '
2046
'deprecated in 2.4. Use revision_ids=[...] instead.',
2047
DeprecationWarning, stacklevel=2)
2048
if revision_ids is not None:
2049
raise AssertionError(
2050
'revision_ids is mutually exclusive with revision_id')
2051
if revision_id is not None:
2052
revision_ids = [revision_id]
2053
inter_repo = _mod_repository.InterRepository.get(other, self)
2054
return inter_repo.search_missing_revision_ids(
2055
find_ghosts=find_ghosts, revision_ids=revision_ids,
2056
if_present_ids=if_present_ids, limit=limit)
2058
def fetch(self, source, revision_id=None, find_ghosts=False,
2060
# No base implementation to use as RemoteRepository is not a subclass
2061
# of Repository; so this is a copy of Repository.fetch().
2062
if fetch_spec is not None and revision_id is not None:
2063
raise AssertionError(
2064
"fetch_spec and revision_id are mutually exclusive.")
2065
if self.is_in_write_group():
2066
raise errors.InternalBzrError(
2067
"May not fetch while in a write group.")
2068
# fast path same-url fetch operations
2069
if (self.has_same_location(source)
2070
and fetch_spec is None
2071
and self._has_same_fallbacks(source)):
2072
# check that last_revision is in 'from' and then return a
2074
if (revision_id is not None and
2075
not _mod_revision.is_null(revision_id)):
2076
self.get_revision(revision_id)
2078
# if there is no specific appropriate InterRepository, this will get
2079
# the InterRepository base class, which raises an
2080
# IncompatibleRepositories when asked to fetch.
2081
inter = _mod_repository.InterRepository.get(source, self)
2082
if (fetch_spec is not None and
2083
not getattr(inter, "supports_fetch_spec", False)):
2084
raise errors.UnsupportedOperation(
2085
"fetch_spec not supported for %r" % inter)
2086
return inter.fetch(revision_id=revision_id,
2087
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2089
def create_bundle(self, target, base, fileobj, format=None):
2091
self._real_repository.create_bundle(target, base, fileobj, format)
2094
@symbol_versioning.deprecated_method(
2095
symbol_versioning.deprecated_in((2, 4, 0)))
2096
def get_ancestry(self, revision_id, topo_sorted=True):
2098
return self._real_repository.get_ancestry(revision_id, topo_sorted)
2100
def fileids_altered_by_revision_ids(self, revision_ids):
2102
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2104
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2106
return self._real_repository._get_versioned_file_checker(
2107
revisions, revision_versions_cache)
2109
def _iter_files_bytes_rpc(self, desired_files, absent):
2110
path = self.bzrdir._path_for_remote_call(self._client)
2113
for (file_id, revid, identifier) in desired_files:
2114
lines.append("%s\0%s" % (
2115
osutils.safe_file_id(file_id),
2116
osutils.safe_revision_id(revid)))
2117
identifiers.append(identifier)
2118
(response_tuple, response_handler) = (
2119
self._call_with_body_bytes_expecting_body(
2120
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2121
if response_tuple != ('ok', ):
2122
response_handler.cancel_read_body()
2123
raise errors.UnexpectedSmartServerResponse(response_tuple)
2124
byte_stream = response_handler.read_streamed_body()
2125
def decompress_stream(start, byte_stream, unused):
2126
decompressor = zlib.decompressobj()
2127
yield decompressor.decompress(start)
2128
while decompressor.unused_data == "":
2130
data = byte_stream.next()
2131
except StopIteration:
2133
yield decompressor.decompress(data)
2134
yield decompressor.flush()
2135
unused.append(decompressor.unused_data)
2138
while not "\n" in unused:
2139
unused += byte_stream.next()
2140
header, rest = unused.split("\n", 1)
2141
args = header.split("\0")
2142
if args[0] == "absent":
2143
absent[identifiers[int(args[3])]] = (args[1], args[2])
2146
elif args[0] == "ok":
2149
raise errors.UnexpectedSmartServerResponse(args)
2151
yield (identifiers[idx],
2152
decompress_stream(rest, byte_stream, unused_chunks))
2153
unused = "".join(unused_chunks)
2155
def iter_files_bytes(self, desired_files):
2156
"""See Repository.iter_file_bytes.
2160
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2161
desired_files, absent):
2162
yield identifier, bytes_iterator
2163
for fallback in self._fallback_repositories:
2166
desired_files = [(key[0], key[1], identifier) for
2167
(identifier, key) in absent.iteritems()]
2168
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2169
del absent[identifier]
2170
yield identifier, bytes_iterator
2172
# There may be more missing items, but raise an exception
2174
missing_identifier = absent.keys()[0]
2175
missing_key = absent[missing_identifier]
2176
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2177
file_id=missing_key[0])
2178
except errors.UnknownSmartMethod:
2180
for (identifier, bytes_iterator) in (
2181
self._real_repository.iter_files_bytes(desired_files)):
2182
yield identifier, bytes_iterator
2184
def get_cached_parent_map(self, revision_ids):
2185
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2186
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2188
def get_parent_map(self, revision_ids):
2189
"""See bzrlib.Graph.get_parent_map()."""
2190
return self._make_parents_provider().get_parent_map(revision_ids)
2192
def _get_parent_map_rpc(self, keys):
2193
"""Helper for get_parent_map that performs the RPC."""
2194
medium = self._client._medium
2195
if medium._is_remote_before((1, 2)):
2196
# We already found out that the server can't understand
2197
# Repository.get_parent_map requests, so just fetch the whole
2200
# Note that this reads the whole graph, when only some keys are
2201
# wanted. On this old server there's no way (?) to get them all
2202
# in one go, and the user probably will have seen a warning about
2203
# the server being old anyhow.
2204
rg = self._get_revision_graph(None)
2205
# There is an API discrepancy between get_parent_map and
2206
# get_revision_graph. Specifically, a "key:()" pair in
2207
# get_revision_graph just means a node has no parents. For
2208
# "get_parent_map" it means the node is a ghost. So fix up the
2209
# graph to correct this.
2210
# https://bugs.launchpad.net/bzr/+bug/214894
2211
# There is one other "bug" which is that ghosts in
2212
# get_revision_graph() are not returned at all. But we won't worry
2213
# about that for now.
2214
for node_id, parent_ids in rg.iteritems():
2215
if parent_ids == ():
2216
rg[node_id] = (NULL_REVISION,)
2217
rg[NULL_REVISION] = ()
2222
raise ValueError('get_parent_map(None) is not valid')
2223
if NULL_REVISION in keys:
2224
keys.discard(NULL_REVISION)
2225
found_parents = {NULL_REVISION:()}
2227
return found_parents
2230
# TODO(Needs analysis): We could assume that the keys being requested
2231
# from get_parent_map are in a breadth first search, so typically they
2232
# will all be depth N from some common parent, and we don't have to
2233
# have the server iterate from the root parent, but rather from the
2234
# keys we're searching; and just tell the server the keyspace we
2235
# already have; but this may be more traffic again.
2237
# Transform self._parents_map into a search request recipe.
2238
# TODO: Manage this incrementally to avoid covering the same path
2239
# repeatedly. (The server will have to on each request, but the less
2240
# work done the better).
2242
# Negative caching notes:
2243
# new server sends missing when a request including the revid
2244
# 'include-missing:' is present in the request.
2245
# missing keys are serialised as missing:X, and we then call
2246
# provider.note_missing(X) for-all X
2247
parents_map = self._unstacked_provider.get_cached_map()
2248
if parents_map is None:
2249
# Repository is not locked, so there's no cache.
2251
if _DEFAULT_SEARCH_DEPTH <= 0:
2252
(start_set, stop_keys,
2253
key_count) = vf_search.search_result_from_parent_map(
2254
parents_map, self._unstacked_provider.missing_keys)
2256
(start_set, stop_keys,
2257
key_count) = vf_search.limited_search_result_from_parent_map(
2258
parents_map, self._unstacked_provider.missing_keys,
2259
keys, depth=_DEFAULT_SEARCH_DEPTH)
2260
recipe = ('manual', start_set, stop_keys, key_count)
2261
body = self._serialise_search_recipe(recipe)
2262
path = self.bzrdir._path_for_remote_call(self._client)
2264
if type(key) is not str:
2266
"key %r not a plain string" % (key,))
2267
verb = 'Repository.get_parent_map'
2268
args = (path, 'include-missing:') + tuple(keys)
2270
response = self._call_with_body_bytes_expecting_body(
2272
except errors.UnknownSmartMethod:
2273
# Server does not support this method, so get the whole graph.
2274
# Worse, we have to force a disconnection, because the server now
2275
# doesn't realise it has a body on the wire to consume, so the
2276
# only way to recover is to abandon the connection.
2278
'Server is too old for fast get_parent_map, reconnecting. '
2279
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2281
# To avoid having to disconnect repeatedly, we keep track of the
2282
# fact the server doesn't understand remote methods added in 1.2.
2283
medium._remember_remote_is_before((1, 2))
2284
# Recurse just once and we should use the fallback code.
2285
return self._get_parent_map_rpc(keys)
2286
response_tuple, response_handler = response
2287
if response_tuple[0] not in ['ok']:
2288
response_handler.cancel_read_body()
2289
raise errors.UnexpectedSmartServerResponse(response_tuple)
2290
if response_tuple[0] == 'ok':
2291
coded = bz2.decompress(response_handler.read_body_bytes())
2293
# no revisions found
2295
lines = coded.split('\n')
2298
d = tuple(line.split())
2300
revision_graph[d[0]] = d[1:]
2303
if d[0].startswith('missing:'):
2305
self._unstacked_provider.note_missing_key(revid)
2307
# no parents - so give the Graph result
2309
revision_graph[d[0]] = (NULL_REVISION,)
2310
return revision_graph
2313
def get_signature_text(self, revision_id):
2314
path = self.bzrdir._path_for_remote_call(self._client)
2316
response_tuple, response_handler = self._call_expecting_body(
2317
'Repository.get_revision_signature_text', path, revision_id)
2318
except errors.UnknownSmartMethod:
2320
return self._real_repository.get_signature_text(revision_id)
2321
except errors.NoSuchRevision, err:
2322
for fallback in self._fallback_repositories:
2324
return fallback.get_signature_text(revision_id)
2325
except errors.NoSuchRevision:
2329
if response_tuple[0] != 'ok':
2330
raise errors.UnexpectedSmartServerResponse(response_tuple)
2331
return response_handler.read_body_bytes()
2334
def _get_inventory_xml(self, revision_id):
2335
# This call is used by older working tree formats,
2336
# which stored a serialized basis inventory.
2338
return self._real_repository._get_inventory_xml(revision_id)
2341
def reconcile(self, other=None, thorough=False):
2342
from bzrlib.reconcile import RepoReconciler
2343
path = self.bzrdir._path_for_remote_call(self._client)
2345
response, handler = self._call_expecting_body(
2346
'Repository.reconcile', path, self._lock_token)
2347
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2349
return self._real_repository.reconcile(other=other, thorough=thorough)
2350
if response != ('ok', ):
2351
raise errors.UnexpectedSmartServerResponse(response)
2352
body = handler.read_body_bytes()
2353
result = RepoReconciler(self)
2354
for line in body.split('\n'):
2357
key, val_text = line.split(':')
2358
if key == "garbage_inventories":
2359
result.garbage_inventories = int(val_text)
2360
elif key == "inconsistent_parents":
2361
result.inconsistent_parents = int(val_text)
2363
mutter("unknown reconcile key %r" % key)
2366
def all_revision_ids(self):
2367
path = self.bzrdir._path_for_remote_call(self._client)
2369
response_tuple, response_handler = self._call_expecting_body(
2370
"Repository.all_revision_ids", path)
2371
except errors.UnknownSmartMethod:
2373
return self._real_repository.all_revision_ids()
2374
if response_tuple != ("ok", ):
2375
raise errors.UnexpectedSmartServerResponse(response_tuple)
2376
revids = set(response_handler.read_body_bytes().splitlines())
2377
for fallback in self._fallback_repositories:
2378
revids.update(set(fallback.all_revision_ids()))
2381
def _filtered_revision_trees(self, revision_ids, file_ids):
2382
"""Return Tree for a revision on this branch with only some files.
2384
:param revision_ids: a sequence of revision-ids;
2385
a revision-id may not be None or 'null:'
2386
:param file_ids: if not None, the result is filtered
2387
so that only those file-ids, their parents and their
2388
children are included.
2390
inventories = self.iter_inventories(revision_ids)
2391
for inv in inventories:
2392
# Should we introduce a FilteredRevisionTree class rather
2393
# than pre-filter the inventory here?
2394
filtered_inv = inv.filter(file_ids)
2395
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2398
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2399
medium = self._client._medium
2400
if medium._is_remote_before((1, 2)):
2402
for delta in self._real_repository.get_deltas_for_revisions(
2403
revisions, specific_fileids):
2406
# Get the revision-ids of interest
2407
required_trees = set()
2408
for revision in revisions:
2409
required_trees.add(revision.revision_id)
2410
required_trees.update(revision.parent_ids[:1])
2412
# Get the matching filtered trees. Note that it's more
2413
# efficient to pass filtered trees to changes_from() rather
2414
# than doing the filtering afterwards. changes_from() could
2415
# arguably do the filtering itself but it's path-based, not
2416
# file-id based, so filtering before or afterwards is
2418
if specific_fileids is None:
2419
trees = dict((t.get_revision_id(), t) for
2420
t in self.revision_trees(required_trees))
2422
trees = dict((t.get_revision_id(), t) for
2423
t in self._filtered_revision_trees(required_trees,
2426
# Calculate the deltas
2427
for revision in revisions:
2428
if not revision.parent_ids:
2429
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2431
old_tree = trees[revision.parent_ids[0]]
2432
yield trees[revision.revision_id].changes_from(old_tree)
2435
def get_revision_delta(self, revision_id, specific_fileids=None):
2436
r = self.get_revision(revision_id)
2437
return list(self.get_deltas_for_revisions([r],
2438
specific_fileids=specific_fileids))[0]
2441
def revision_trees(self, revision_ids):
2442
inventories = self.iter_inventories(revision_ids)
2443
for inv in inventories:
2444
yield InventoryRevisionTree(self, inv, inv.revision_id)
2447
def get_revision_reconcile(self, revision_id):
2449
return self._real_repository.get_revision_reconcile(revision_id)
2452
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2454
return self._real_repository.check(revision_ids=revision_ids,
2455
callback_refs=callback_refs, check_repo=check_repo)
2457
def copy_content_into(self, destination, revision_id=None):
2458
"""Make a complete copy of the content in self into destination.
2460
This is a destructive operation! Do not use it on existing
2463
interrepo = _mod_repository.InterRepository.get(self, destination)
2464
return interrepo.copy_content(revision_id)
2466
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2467
# get a tarball of the remote repository, and copy from that into the
2470
# TODO: Maybe a progress bar while streaming the tarball?
2471
note(gettext("Copying repository content as tarball..."))
2472
tar_file = self._get_tarball('bz2')
2473
if tar_file is None:
2475
destination = to_bzrdir.create_repository()
2477
tar = tarfile.open('repository', fileobj=tar_file,
2479
tmpdir = osutils.mkdtemp()
2481
_extract_tar(tar, tmpdir)
2482
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2483
tmp_repo = tmp_bzrdir.open_repository()
2484
tmp_repo.copy_content_into(destination, revision_id)
2486
osutils.rmtree(tmpdir)
2490
# TODO: Suggestion from john: using external tar is much faster than
2491
# python's tarfile library, but it may not work on windows.
2494
def inventories(self):
2495
"""Decorate the real repository for now.
2497
In the long term a full blown network facility is needed to
2498
avoid creating a real repository object locally.
2501
return self._real_repository.inventories
2504
def pack(self, hint=None, clean_obsolete_packs=False):
2505
"""Compress the data within the repository.
2510
body = "".join([l+"\n" for l in hint])
2511
path = self.bzrdir._path_for_remote_call(self._client)
2513
response, handler = self._call_with_body_bytes_expecting_body(
2514
'Repository.pack', (path, self._lock_token,
2515
str(clean_obsolete_packs)), body)
2516
except errors.UnknownSmartMethod:
2518
return self._real_repository.pack(hint=hint,
2519
clean_obsolete_packs=clean_obsolete_packs)
2520
handler.cancel_read_body()
2521
if response != ('ok', ):
2522
raise errors.UnexpectedSmartServerResponse(response)
2525
def revisions(self):
2526
"""Decorate the real repository for now.
2528
In the long term a full blown network facility is needed.
2531
return self._real_repository.revisions
2533
def set_make_working_trees(self, new_value):
2535
new_value_str = "True"
2537
new_value_str = "False"
2538
path = self.bzrdir._path_for_remote_call(self._client)
2540
response = self._call(
2541
'Repository.set_make_working_trees', path, new_value_str)
2542
except errors.UnknownSmartMethod:
2544
self._real_repository.set_make_working_trees(new_value)
2546
if response[0] != 'ok':
2547
raise errors.UnexpectedSmartServerResponse(response)
2550
def signatures(self):
2551
"""Decorate the real repository for now.
2553
In the long term a full blown network facility is needed to avoid
2554
creating a real repository object locally.
2557
return self._real_repository.signatures
2560
def sign_revision(self, revision_id, gpg_strategy):
2561
testament = _mod_testament.Testament.from_revision(self, revision_id)
2562
plaintext = testament.as_short_text()
2563
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2567
"""Decorate the real repository for now.
2569
In the long term a full blown network facility is needed to avoid
2570
creating a real repository object locally.
2573
return self._real_repository.texts
2575
def _iter_revisions_rpc(self, revision_ids):
2576
body = "\n".join(revision_ids)
2577
path = self.bzrdir._path_for_remote_call(self._client)
2578
response_tuple, response_handler = (
2579
self._call_with_body_bytes_expecting_body(
2580
"Repository.iter_revisions", (path, ), body))
2581
if response_tuple[0] != "ok":
2582
raise errors.UnexpectedSmartServerResponse(response_tuple)
2583
serializer_format = response_tuple[1]
2584
serializer = serializer_format_registry.get(serializer_format)
2585
byte_stream = response_handler.read_streamed_body()
2586
decompressor = zlib.decompressobj()
2588
for bytes in byte_stream:
2589
chunks.append(decompressor.decompress(bytes))
2590
if decompressor.unused_data != "":
2591
chunks.append(decompressor.flush())
2592
yield serializer.read_revision_from_string("".join(chunks))
2593
unused = decompressor.unused_data
2594
decompressor = zlib.decompressobj()
2595
chunks = [decompressor.decompress(unused)]
2596
chunks.append(decompressor.flush())
2597
text = "".join(chunks)
2599
yield serializer.read_revision_from_string("".join(chunks))
2602
def get_revisions(self, revision_ids):
2603
if revision_ids is None:
2604
revision_ids = self.all_revision_ids()
2606
for rev_id in revision_ids:
2607
if not rev_id or not isinstance(rev_id, basestring):
2608
raise errors.InvalidRevisionId(
2609
revision_id=rev_id, branch=self)
2611
missing = set(revision_ids)
2613
for rev in self._iter_revisions_rpc(revision_ids):
2614
missing.remove(rev.revision_id)
2615
revs[rev.revision_id] = rev
2616
except errors.UnknownSmartMethod:
2618
return self._real_repository.get_revisions(revision_ids)
2619
for fallback in self._fallback_repositories:
2622
for revid in list(missing):
2623
# XXX JRV 2011-11-20: It would be nice if there was a
2624
# public method on Repository that could be used to query
2625
# for revision objects *without* failing completely if one
2626
# was missing. There is VersionedFileRepository._iter_revisions,
2627
# but unfortunately that's private and not provided by
2628
# all repository implementations.
2630
revs[revid] = fallback.get_revision(revid)
2631
except errors.NoSuchRevision:
2634
missing.remove(revid)
2636
raise errors.NoSuchRevision(self, list(missing)[0])
2637
return [revs[revid] for revid in revision_ids]
2639
def supports_rich_root(self):
2640
return self._format.rich_root_data
2642
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2643
def iter_reverse_revision_history(self, revision_id):
2645
return self._real_repository.iter_reverse_revision_history(revision_id)
2648
def _serializer(self):
2649
return self._format._serializer
2652
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2653
signature = gpg_strategy.sign(plaintext)
2654
self.add_signature_text(revision_id, signature)
2656
def add_signature_text(self, revision_id, signature):
2657
if self._real_repository:
2658
# If there is a real repository the write group will
2659
# be in the real repository as well, so use that:
2661
return self._real_repository.add_signature_text(
2662
revision_id, signature)
2663
path = self.bzrdir._path_for_remote_call(self._client)
2664
response, handler = self._call_with_body_bytes_expecting_body(
2665
'Repository.add_signature_text', (path, self._lock_token,
2666
revision_id) + tuple(self._write_group_tokens), signature)
2667
handler.cancel_read_body()
2669
if response[0] != 'ok':
2670
raise errors.UnexpectedSmartServerResponse(response)
2671
self._write_group_tokens = response[1:]
2673
def has_signature_for_revision_id(self, revision_id):
2674
path = self.bzrdir._path_for_remote_call(self._client)
2676
response = self._call('Repository.has_signature_for_revision_id',
2678
except errors.UnknownSmartMethod:
2680
return self._real_repository.has_signature_for_revision_id(
2682
if response[0] not in ('yes', 'no'):
2683
raise SmartProtocolError('unexpected response code %s' % (response,))
2684
if response[0] == 'yes':
2686
for fallback in self._fallback_repositories:
2687
if fallback.has_signature_for_revision_id(revision_id):
2692
def verify_revision_signature(self, revision_id, gpg_strategy):
2693
if not self.has_signature_for_revision_id(revision_id):
2694
return gpg.SIGNATURE_NOT_SIGNED, None
2695
signature = self.get_signature_text(revision_id)
2697
testament = _mod_testament.Testament.from_revision(self, revision_id)
2698
plaintext = testament.as_short_text()
2700
return gpg_strategy.verify(signature, plaintext)
2702
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2704
return self._real_repository.item_keys_introduced_by(revision_ids,
2705
_files_pb=_files_pb)
2707
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2709
return self._real_repository._find_inconsistent_revision_parents(
2712
def _check_for_inconsistent_revision_parents(self):
2714
return self._real_repository._check_for_inconsistent_revision_parents()
2716
def _make_parents_provider(self, other=None):
2717
providers = [self._unstacked_provider]
2718
if other is not None:
2719
providers.insert(0, other)
2720
return graph.StackedParentsProvider(_LazyListJoin(
2721
providers, self._fallback_repositories))
2723
def _serialise_search_recipe(self, recipe):
2724
"""Serialise a graph search recipe.
2726
:param recipe: A search recipe (start, stop, count).
2727
:return: Serialised bytes.
2729
start_keys = ' '.join(recipe[1])
2730
stop_keys = ' '.join(recipe[2])
2731
count = str(recipe[3])
2732
return '\n'.join((start_keys, stop_keys, count))
2734
def _serialise_search_result(self, search_result):
2735
parts = search_result.get_network_struct()
2736
return '\n'.join(parts)
2739
path = self.bzrdir._path_for_remote_call(self._client)
2741
response = self._call('PackRepository.autopack', path)
2742
except errors.UnknownSmartMethod:
2744
self._real_repository._pack_collection.autopack()
2747
if response[0] != 'ok':
2748
raise errors.UnexpectedSmartServerResponse(response)
2751
class RemoteStreamSink(vf_repository.StreamSink):
2753
def _insert_real(self, stream, src_format, resume_tokens):
2754
self.target_repo._ensure_real()
2755
sink = self.target_repo._real_repository._get_sink()
2756
result = sink.insert_stream(stream, src_format, resume_tokens)
2758
self.target_repo.autopack()
2761
def insert_stream(self, stream, src_format, resume_tokens):
2762
target = self.target_repo
2763
target._unstacked_provider.missing_keys.clear()
2764
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2765
if target._lock_token:
2766
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2767
lock_args = (target._lock_token or '',)
2769
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2771
client = target._client
2772
medium = client._medium
2773
path = target.bzrdir._path_for_remote_call(client)
2774
# Probe for the verb to use with an empty stream before sending the
2775
# real stream to it. We do this both to avoid the risk of sending a
2776
# large request that is then rejected, and because we don't want to
2777
# implement a way to buffer, rewind, or restart the stream.
2779
for verb, required_version in candidate_calls:
2780
if medium._is_remote_before(required_version):
2783
# We've already done the probing (and set _is_remote_before) on
2784
# a previous insert.
2787
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2789
response = client.call_with_body_stream(
2790
(verb, path, '') + lock_args, byte_stream)
2791
except errors.UnknownSmartMethod:
2792
medium._remember_remote_is_before(required_version)
2798
return self._insert_real(stream, src_format, resume_tokens)
2799
self._last_inv_record = None
2800
self._last_substream = None
2801
if required_version < (1, 19):
2802
# Remote side doesn't support inventory deltas. Wrap the stream to
2803
# make sure we don't send any. If the stream contains inventory
2804
# deltas we'll interrupt the smart insert_stream request and
2806
stream = self._stop_stream_if_inventory_delta(stream)
2807
byte_stream = smart_repo._stream_to_byte_stream(
2809
resume_tokens = ' '.join(resume_tokens)
2810
response = client.call_with_body_stream(
2811
(verb, path, resume_tokens) + lock_args, byte_stream)
2812
if response[0][0] not in ('ok', 'missing-basis'):
2813
raise errors.UnexpectedSmartServerResponse(response)
2814
if self._last_substream is not None:
2815
# The stream included an inventory-delta record, but the remote
2816
# side isn't new enough to support them. So we need to send the
2817
# rest of the stream via VFS.
2818
self.target_repo.refresh_data()
2819
return self._resume_stream_with_vfs(response, src_format)
2820
if response[0][0] == 'missing-basis':
2821
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2822
resume_tokens = tokens
2823
return resume_tokens, set(missing_keys)
2825
self.target_repo.refresh_data()
2828
def _resume_stream_with_vfs(self, response, src_format):
2829
"""Resume sending a stream via VFS, first resending the record and
2830
substream that couldn't be sent via an insert_stream verb.
2832
if response[0][0] == 'missing-basis':
2833
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2834
# Ignore missing_keys, we haven't finished inserting yet
2837
def resume_substream():
2838
# Yield the substream that was interrupted.
2839
for record in self._last_substream:
2841
self._last_substream = None
2842
def resume_stream():
2843
# Finish sending the interrupted substream
2844
yield ('inventory-deltas', resume_substream())
2845
# Then simply continue sending the rest of the stream.
2846
for substream_kind, substream in self._last_stream:
2847
yield substream_kind, substream
2848
return self._insert_real(resume_stream(), src_format, tokens)
2850
def _stop_stream_if_inventory_delta(self, stream):
2851
"""Normally this just lets the original stream pass-through unchanged.
2853
However if any 'inventory-deltas' substream occurs it will stop
2854
streaming, and store the interrupted substream and stream in
2855
self._last_substream and self._last_stream so that the stream can be
2856
resumed by _resume_stream_with_vfs.
2859
stream_iter = iter(stream)
2860
for substream_kind, substream in stream_iter:
2861
if substream_kind == 'inventory-deltas':
2862
self._last_substream = substream
2863
self._last_stream = stream_iter
2866
yield substream_kind, substream
2869
class RemoteStreamSource(vf_repository.StreamSource):
2870
"""Stream data from a remote server."""
2872
def get_stream(self, search):
2873
if (self.from_repository._fallback_repositories and
2874
self.to_format._fetch_order == 'topological'):
2875
return self._real_stream(self.from_repository, search)
2878
repos = [self.from_repository]
2884
repos.extend(repo._fallback_repositories)
2885
sources.append(repo)
2886
return self.missing_parents_chain(search, sources)
2888
def get_stream_for_missing_keys(self, missing_keys):
2889
self.from_repository._ensure_real()
2890
real_repo = self.from_repository._real_repository
2891
real_source = real_repo._get_source(self.to_format)
2892
return real_source.get_stream_for_missing_keys(missing_keys)
2894
def _real_stream(self, repo, search):
2895
"""Get a stream for search from repo.
2897
This never called RemoteStreamSource.get_stream, and is a helper
2898
for RemoteStreamSource._get_stream to allow getting a stream
2899
reliably whether fallback back because of old servers or trying
2900
to stream from a non-RemoteRepository (which the stacked support
2903
source = repo._get_source(self.to_format)
2904
if isinstance(source, RemoteStreamSource):
2906
source = repo._real_repository._get_source(self.to_format)
2907
return source.get_stream(search)
2909
def _get_stream(self, repo, search):
2910
"""Core worker to get a stream from repo for search.
2912
This is used by both get_stream and the stacking support logic. It
2913
deliberately gets a stream for repo which does not need to be
2914
self.from_repository. In the event that repo is not Remote, or
2915
cannot do a smart stream, a fallback is made to the generic
2916
repository._get_stream() interface, via self._real_stream.
2918
In the event of stacking, streams from _get_stream will not
2919
contain all the data for search - this is normal (see get_stream).
2921
:param repo: A repository.
2922
:param search: A search.
2924
# Fallbacks may be non-smart
2925
if not isinstance(repo, RemoteRepository):
2926
return self._real_stream(repo, search)
2927
client = repo._client
2928
medium = client._medium
2929
path = repo.bzrdir._path_for_remote_call(client)
2930
search_bytes = repo._serialise_search_result(search)
2931
args = (path, self.to_format.network_name())
2933
('Repository.get_stream_1.19', (1, 19)),
2934
('Repository.get_stream', (1, 13))]
2937
for verb, version in candidate_verbs:
2938
if medium._is_remote_before(version):
2941
response = repo._call_with_body_bytes_expecting_body(
2942
verb, args, search_bytes)
2943
except errors.UnknownSmartMethod:
2944
medium._remember_remote_is_before(version)
2945
except errors.UnknownErrorFromSmartServer, e:
2946
if isinstance(search, vf_search.EverythingResult):
2947
error_verb = e.error_from_smart_server.error_verb
2948
if error_verb == 'BadSearch':
2949
# Pre-2.4 servers don't support this sort of search.
2950
# XXX: perhaps falling back to VFS on BadSearch is a
2951
# good idea in general? It might provide a little bit
2952
# of protection against client-side bugs.
2953
medium._remember_remote_is_before((2, 4))
2957
response_tuple, response_handler = response
2961
return self._real_stream(repo, search)
2962
if response_tuple[0] != 'ok':
2963
raise errors.UnexpectedSmartServerResponse(response_tuple)
2964
byte_stream = response_handler.read_streamed_body()
2965
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2966
self._record_counter)
2967
if src_format.network_name() != repo._format.network_name():
2968
raise AssertionError(
2969
"Mismatched RemoteRepository and stream src %r, %r" % (
2970
src_format.network_name(), repo._format.network_name()))
2973
def missing_parents_chain(self, search, sources):
2974
"""Chain multiple streams together to handle stacking.
2976
:param search: The overall search to satisfy with streams.
2977
:param sources: A list of Repository objects to query.
2979
self.from_serialiser = self.from_repository._format._serializer
2980
self.seen_revs = set()
2981
self.referenced_revs = set()
2982
# If there are heads in the search, or the key count is > 0, we are not
2984
while not search.is_empty() and len(sources) > 1:
2985
source = sources.pop(0)
2986
stream = self._get_stream(source, search)
2987
for kind, substream in stream:
2988
if kind != 'revisions':
2989
yield kind, substream
2991
yield kind, self.missing_parents_rev_handler(substream)
2992
search = search.refine(self.seen_revs, self.referenced_revs)
2993
self.seen_revs = set()
2994
self.referenced_revs = set()
2995
if not search.is_empty():
2996
for kind, stream in self._get_stream(sources[0], search):
2999
def missing_parents_rev_handler(self, substream):
3000
for content in substream:
3001
revision_bytes = content.get_bytes_as('fulltext')
3002
revision = self.from_serialiser.read_revision_from_string(
3004
self.seen_revs.add(content.key[-1])
3005
self.referenced_revs.update(revision.parent_ids)
3009
class RemoteBranchLockableFiles(LockableFiles):
3010
"""A 'LockableFiles' implementation that talks to a smart server.
3012
This is not a public interface class.
3015
def __init__(self, bzrdir, _client):
3016
self.bzrdir = bzrdir
3017
self._client = _client
3018
self._need_find_modes = True
3019
LockableFiles.__init__(
3020
self, bzrdir.get_branch_transport(None),
3021
'lock', lockdir.LockDir)
3023
def _find_modes(self):
3024
# RemoteBranches don't let the client set the mode of control files.
3025
self._dir_mode = None
3026
self._file_mode = None
3029
class RemoteBranchFormat(branch.BranchFormat):
3031
def __init__(self, network_name=None):
3032
super(RemoteBranchFormat, self).__init__()
3033
self._matchingbzrdir = RemoteBzrDirFormat()
3034
self._matchingbzrdir.set_branch_format(self)
3035
self._custom_format = None
3036
self._network_name = network_name
3038
def __eq__(self, other):
3039
return (isinstance(other, RemoteBranchFormat) and
3040
self.__dict__ == other.__dict__)
3042
def _ensure_real(self):
3043
if self._custom_format is None:
3045
self._custom_format = branch.network_format_registry.get(
3048
raise errors.UnknownFormatError(kind='branch',
3049
format=self._network_name)
3051
def get_format_description(self):
3053
return 'Remote: ' + self._custom_format.get_format_description()
3055
def network_name(self):
3056
return self._network_name
3058
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3059
return a_bzrdir.open_branch(name=name,
3060
ignore_fallbacks=ignore_fallbacks)
3062
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3063
# Initialisation when using a local bzrdir object, or a non-vfs init
3064
# method is not available on the server.
3065
# self._custom_format is always set - the start of initialize ensures
3067
if isinstance(a_bzrdir, RemoteBzrDir):
3068
a_bzrdir._ensure_real()
3069
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3070
name, append_revisions_only=append_revisions_only)
3072
# We assume the bzrdir is parameterised; it may not be.
3073
result = self._custom_format.initialize(a_bzrdir, name,
3074
append_revisions_only=append_revisions_only)
3075
if (isinstance(a_bzrdir, RemoteBzrDir) and
3076
not isinstance(result, RemoteBranch)):
3077
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3081
def initialize(self, a_bzrdir, name=None, repository=None,
3082
append_revisions_only=None):
3083
# 1) get the network name to use.
3084
if self._custom_format:
3085
network_name = self._custom_format.network_name()
3087
# Select the current bzrlib default and ask for that.
3088
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3089
reference_format = reference_bzrdir_format.get_branch_format()
3090
self._custom_format = reference_format
3091
network_name = reference_format.network_name()
3092
# Being asked to create on a non RemoteBzrDir:
3093
if not isinstance(a_bzrdir, RemoteBzrDir):
3094
return self._vfs_initialize(a_bzrdir, name=name,
3095
append_revisions_only=append_revisions_only)
3096
medium = a_bzrdir._client._medium
3097
if medium._is_remote_before((1, 13)):
3098
return self._vfs_initialize(a_bzrdir, name=name,
3099
append_revisions_only=append_revisions_only)
3100
# Creating on a remote bzr dir.
3101
# 2) try direct creation via RPC
3102
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3103
if name is not None:
3104
# XXX JRV20100304: Support creating colocated branches
3105
raise errors.NoColocatedBranchSupport(self)
3106
verb = 'BzrDir.create_branch'
3108
response = a_bzrdir._call(verb, path, network_name)
3109
except errors.UnknownSmartMethod:
3110
# Fallback - use vfs methods
3111
medium._remember_remote_is_before((1, 13))
3112
return self._vfs_initialize(a_bzrdir, name=name,
3113
append_revisions_only=append_revisions_only)
3114
if response[0] != 'ok':
3115
raise errors.UnexpectedSmartServerResponse(response)
3116
# Turn the response into a RemoteRepository object.
3117
format = RemoteBranchFormat(network_name=response[1])
3118
repo_format = response_tuple_to_repo_format(response[3:])
3119
repo_path = response[2]
3120
if repository is not None:
3121
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3122
url_diff = urlutils.relative_url(repository.user_url,
3125
raise AssertionError(
3126
'repository.user_url %r does not match URL from server '
3127
'response (%r + %r)'
3128
% (repository.user_url, a_bzrdir.user_url, repo_path))
3129
remote_repo = repository
3132
repo_bzrdir = a_bzrdir
3134
repo_bzrdir = RemoteBzrDir(
3135
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3137
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3138
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3139
format=format, setup_stacking=False, name=name)
3140
if append_revisions_only:
3141
remote_branch.set_append_revisions_only(append_revisions_only)
3142
# XXX: We know this is a new branch, so it must have revno 0, revid
3143
# NULL_REVISION. Creating the branch locked would make this be unable
3144
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3145
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3146
return remote_branch
3148
def make_tags(self, branch):
3150
return self._custom_format.make_tags(branch)
3152
def supports_tags(self):
3153
# Remote branches might support tags, but we won't know until we
3154
# access the real remote branch.
3156
return self._custom_format.supports_tags()
3158
def supports_stacking(self):
3160
return self._custom_format.supports_stacking()
3162
def supports_set_append_revisions_only(self):
3164
return self._custom_format.supports_set_append_revisions_only()
3166
def _use_default_local_heads_to_fetch(self):
3167
# If the branch format is a metadir format *and* its heads_to_fetch
3168
# implementation is not overridden vs the base class, we can use the
3169
# base class logic rather than use the heads_to_fetch RPC. This is
3170
# usually cheaper in terms of net round trips, as the last-revision and
3171
# tags info fetched is cached and would be fetched anyway.
3173
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3174
branch_class = self._custom_format._branch_class()
3175
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3176
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3181
class RemoteBranchStore(config.IniFileStore):
3182
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3184
Note that this is specific to bzr-based formats.
3187
def __init__(self, branch):
3188
super(RemoteBranchStore, self).__init__()
3189
self.branch = branch
3191
self._real_store = None
3193
def lock_write(self, token=None):
3194
return self.branch.lock_write(token)
3197
return self.branch.unlock()
3201
# We need to be able to override the undecorated implementation
3202
self.save_without_locking()
3204
def save_without_locking(self):
3205
super(RemoteBranchStore, self).save()
3207
def external_url(self):
3208
return self.branch.user_url
3210
def _load_content(self):
3211
path = self.branch._remote_path()
3213
response, handler = self.branch._call_expecting_body(
3214
'Branch.get_config_file', path)
3215
except errors.UnknownSmartMethod:
3217
return self._real_store._load_content()
3218
if len(response) and response[0] != 'ok':
3219
raise errors.UnexpectedSmartServerResponse(response)
3220
return handler.read_body_bytes()
3222
def _save_content(self, content):
3223
path = self.branch._remote_path()
3225
response, handler = self.branch._call_with_body_bytes_expecting_body(
3226
'Branch.put_config_file', (path,
3227
self.branch._lock_token, self.branch._repo_lock_token),
3229
except errors.UnknownSmartMethod:
3231
return self._real_store._save_content(content)
3232
handler.cancel_read_body()
3233
if response != ('ok', ):
3234
raise errors.UnexpectedSmartServerResponse(response)
3236
def _ensure_real(self):
3237
self.branch._ensure_real()
3238
if self._real_store is None:
3239
self._real_store = config.BranchStore(self.branch)
3242
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3243
"""Branch stored on a server accessed by HPSS RPC.
3245
At the moment most operations are mapped down to simple file operations.
3248
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3249
_client=None, format=None, setup_stacking=True, name=None,
3250
possible_transports=None):
3251
"""Create a RemoteBranch instance.
3253
:param real_branch: An optional local implementation of the branch
3254
format, usually accessing the data via the VFS.
3255
:param _client: Private parameter for testing.
3256
:param format: A RemoteBranchFormat object, None to create one
3257
automatically. If supplied it should have a network_name already
3259
:param setup_stacking: If True make an RPC call to determine the
3260
stacked (or not) status of the branch. If False assume the branch
3262
:param name: Colocated branch name
3264
# We intentionally don't call the parent class's __init__, because it
3265
# will try to assign to self.tags, which is a property in this subclass.
3266
# And the parent's __init__ doesn't do much anyway.
3267
self.bzrdir = remote_bzrdir
3268
if _client is not None:
3269
self._client = _client
3271
self._client = remote_bzrdir._client
3272
self.repository = remote_repository
3273
if real_branch is not None:
3274
self._real_branch = real_branch
3275
# Give the remote repository the matching real repo.
3276
real_repo = self._real_branch.repository
3277
if isinstance(real_repo, RemoteRepository):
3278
real_repo._ensure_real()
3279
real_repo = real_repo._real_repository
3280
self.repository._set_real_repository(real_repo)
3281
# Give the branch the remote repository to let fast-pathing happen.
3282
self._real_branch.repository = self.repository
3284
self._real_branch = None
3285
# Fill out expected attributes of branch for bzrlib API users.
3286
self._clear_cached_state()
3287
# TODO: deprecate self.base in favor of user_url
3288
self.base = self.bzrdir.user_url
3290
self._control_files = None
3291
self._lock_mode = None
3292
self._lock_token = None
3293
self._repo_lock_token = None
3294
self._lock_count = 0
3295
self._leave_lock = False
3296
# Setup a format: note that we cannot call _ensure_real until all the
3297
# attributes above are set: This code cannot be moved higher up in this
3300
self._format = RemoteBranchFormat()
3301
if real_branch is not None:
3302
self._format._network_name = \
3303
self._real_branch._format.network_name()
3305
self._format = format
3306
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3307
# branch.open_branch method.
3308
self._real_ignore_fallbacks = not setup_stacking
3309
if not self._format._network_name:
3310
# Did not get from open_branchV2 - old server.
3312
self._format._network_name = \
3313
self._real_branch._format.network_name()
3314
self.tags = self._format.make_tags(self)
3315
# The base class init is not called, so we duplicate this:
3316
hooks = branch.Branch.hooks['open']
3319
self._is_stacked = False
3321
self._setup_stacking(possible_transports)
3323
def _setup_stacking(self, possible_transports):
3324
# configure stacking into the remote repository, by reading it from
3327
fallback_url = self.get_stacked_on_url()
3328
except (errors.NotStacked, errors.UnstackableBranchFormat,
3329
errors.UnstackableRepositoryFormat), e:
3331
self._is_stacked = True
3332
if possible_transports is None:
3333
possible_transports = []
3335
possible_transports = list(possible_transports)
3336
possible_transports.append(self.bzrdir.root_transport)
3337
self._activate_fallback_location(fallback_url,
3338
possible_transports=possible_transports)
3340
def _get_config(self):
3341
return RemoteBranchConfig(self)
3343
def _get_config_store(self):
3344
return RemoteBranchStore(self)
3346
def _get_real_transport(self):
3347
# if we try vfs access, return the real branch's vfs transport
3349
return self._real_branch._transport
3351
_transport = property(_get_real_transport)
3354
return "%s(%s)" % (self.__class__.__name__, self.base)
3358
def _ensure_real(self):
3359
"""Ensure that there is a _real_branch set.
3361
Used before calls to self._real_branch.
3363
if self._real_branch is None:
3364
if not vfs.vfs_enabled():
3365
raise AssertionError('smart server vfs must be enabled '
3366
'to use vfs implementation')
3367
self.bzrdir._ensure_real()
3368
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3369
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3370
if self.repository._real_repository is None:
3371
# Give the remote repository the matching real repo.
3372
real_repo = self._real_branch.repository
3373
if isinstance(real_repo, RemoteRepository):
3374
real_repo._ensure_real()
3375
real_repo = real_repo._real_repository
3376
self.repository._set_real_repository(real_repo)
3377
# Give the real branch the remote repository to let fast-pathing
3379
self._real_branch.repository = self.repository
3380
if self._lock_mode == 'r':
3381
self._real_branch.lock_read()
3382
elif self._lock_mode == 'w':
3383
self._real_branch.lock_write(token=self._lock_token)
3385
def _translate_error(self, err, **context):
3386
self.repository._translate_error(err, branch=self, **context)
3388
def _clear_cached_state(self):
3389
super(RemoteBranch, self)._clear_cached_state()
3390
if self._real_branch is not None:
3391
self._real_branch._clear_cached_state()
3393
def _clear_cached_state_of_remote_branch_only(self):
3394
"""Like _clear_cached_state, but doesn't clear the cache of
3397
This is useful when falling back to calling a method of
3398
self._real_branch that changes state. In that case the underlying
3399
branch changes, so we need to invalidate this RemoteBranch's cache of
3400
it. However, there's no need to invalidate the _real_branch's cache
3401
too, in fact doing so might harm performance.
3403
super(RemoteBranch, self)._clear_cached_state()
3406
def control_files(self):
3407
# Defer actually creating RemoteBranchLockableFiles until its needed,
3408
# because it triggers an _ensure_real that we otherwise might not need.
3409
if self._control_files is None:
3410
self._control_files = RemoteBranchLockableFiles(
3411
self.bzrdir, self._client)
3412
return self._control_files
3414
def get_physical_lock_status(self):
3415
"""See Branch.get_physical_lock_status()."""
3417
response = self._client.call('Branch.get_physical_lock_status',
3418
self._remote_path())
3419
except errors.UnknownSmartMethod:
3421
return self._real_branch.get_physical_lock_status()
3422
if response[0] not in ('yes', 'no'):
3423
raise errors.UnexpectedSmartServerResponse(response)
3424
return (response[0] == 'yes')
3426
def get_stacked_on_url(self):
3427
"""Get the URL this branch is stacked against.
3429
:raises NotStacked: If the branch is not stacked.
3430
:raises UnstackableBranchFormat: If the branch does not support
3432
:raises UnstackableRepositoryFormat: If the repository does not support
3436
# there may not be a repository yet, so we can't use
3437
# self._translate_error, so we can't use self._call either.
3438
response = self._client.call('Branch.get_stacked_on_url',
3439
self._remote_path())
3440
except errors.ErrorFromSmartServer, err:
3441
# there may not be a repository yet, so we can't call through
3442
# its _translate_error
3443
_translate_error(err, branch=self)
3444
except errors.UnknownSmartMethod, err:
3446
return self._real_branch.get_stacked_on_url()
3447
if response[0] != 'ok':
3448
raise errors.UnexpectedSmartServerResponse(response)
3451
def set_stacked_on_url(self, url):
3452
branch.Branch.set_stacked_on_url(self, url)
3454
self._is_stacked = False
3456
self._is_stacked = True
3458
def _vfs_get_tags_bytes(self):
3460
return self._real_branch._get_tags_bytes()
3463
def _get_tags_bytes(self):
3464
if self._tags_bytes is None:
3465
self._tags_bytes = self._get_tags_bytes_via_hpss()
3466
return self._tags_bytes
3468
def _get_tags_bytes_via_hpss(self):
3469
medium = self._client._medium
3470
if medium._is_remote_before((1, 13)):
3471
return self._vfs_get_tags_bytes()
3473
response = self._call('Branch.get_tags_bytes', self._remote_path())
3474
except errors.UnknownSmartMethod:
3475
medium._remember_remote_is_before((1, 13))
3476
return self._vfs_get_tags_bytes()
3479
def _vfs_set_tags_bytes(self, bytes):
3481
return self._real_branch._set_tags_bytes(bytes)
3483
def _set_tags_bytes(self, bytes):
3484
if self.is_locked():
3485
self._tags_bytes = bytes
3486
medium = self._client._medium
3487
if medium._is_remote_before((1, 18)):
3488
self._vfs_set_tags_bytes(bytes)
3492
self._remote_path(), self._lock_token, self._repo_lock_token)
3493
response = self._call_with_body_bytes(
3494
'Branch.set_tags_bytes', args, bytes)
3495
except errors.UnknownSmartMethod:
3496
medium._remember_remote_is_before((1, 18))
3497
self._vfs_set_tags_bytes(bytes)
3499
def lock_read(self):
3500
"""Lock the branch for read operations.
3502
:return: A bzrlib.lock.LogicalLockResult.
3504
self.repository.lock_read()
3505
if not self._lock_mode:
3506
self._note_lock('r')
3507
self._lock_mode = 'r'
3508
self._lock_count = 1
3509
if self._real_branch is not None:
3510
self._real_branch.lock_read()
3512
self._lock_count += 1
3513
return lock.LogicalLockResult(self.unlock)
3515
def _remote_lock_write(self, token):
3517
branch_token = repo_token = ''
3519
branch_token = token
3520
repo_token = self.repository.lock_write().repository_token
3521
self.repository.unlock()
3522
err_context = {'token': token}
3524
response = self._call(
3525
'Branch.lock_write', self._remote_path(), branch_token,
3526
repo_token or '', **err_context)
3527
except errors.LockContention, e:
3528
# The LockContention from the server doesn't have any
3529
# information about the lock_url. We re-raise LockContention
3530
# with valid lock_url.
3531
raise errors.LockContention('(remote lock)',
3532
self.repository.base.split('.bzr/')[0])
3533
if response[0] != 'ok':
3534
raise errors.UnexpectedSmartServerResponse(response)
3535
ok, branch_token, repo_token = response
3536
return branch_token, repo_token
3538
def lock_write(self, token=None):
3539
if not self._lock_mode:
3540
self._note_lock('w')
3541
# Lock the branch and repo in one remote call.
3542
remote_tokens = self._remote_lock_write(token)
3543
self._lock_token, self._repo_lock_token = remote_tokens
3544
if not self._lock_token:
3545
raise SmartProtocolError('Remote server did not return a token!')
3546
# Tell the self.repository object that it is locked.
3547
self.repository.lock_write(
3548
self._repo_lock_token, _skip_rpc=True)
3550
if self._real_branch is not None:
3551
self._real_branch.lock_write(token=self._lock_token)
3552
if token is not None:
3553
self._leave_lock = True
3555
self._leave_lock = False
3556
self._lock_mode = 'w'
3557
self._lock_count = 1
3558
elif self._lock_mode == 'r':
3559
raise errors.ReadOnlyError(self)
3561
if token is not None:
3562
# A token was given to lock_write, and we're relocking, so
3563
# check that the given token actually matches the one we
3565
if token != self._lock_token:
3566
raise errors.TokenMismatch(token, self._lock_token)
3567
self._lock_count += 1
3568
# Re-lock the repository too.
3569
self.repository.lock_write(self._repo_lock_token)
3570
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3572
def _unlock(self, branch_token, repo_token):
3573
err_context = {'token': str((branch_token, repo_token))}
3574
response = self._call(
3575
'Branch.unlock', self._remote_path(), branch_token,
3576
repo_token or '', **err_context)
3577
if response == ('ok',):
3579
raise errors.UnexpectedSmartServerResponse(response)
3581
@only_raises(errors.LockNotHeld, errors.LockBroken)
3584
self._lock_count -= 1
3585
if not self._lock_count:
3586
self._clear_cached_state()
3587
mode = self._lock_mode
3588
self._lock_mode = None
3589
if self._real_branch is not None:
3590
if (not self._leave_lock and mode == 'w' and
3591
self._repo_lock_token):
3592
# If this RemoteBranch will remove the physical lock
3593
# for the repository, make sure the _real_branch
3594
# doesn't do it first. (Because the _real_branch's
3595
# repository is set to be the RemoteRepository.)
3596
self._real_branch.repository.leave_lock_in_place()
3597
self._real_branch.unlock()
3599
# Only write-locked branched need to make a remote method
3600
# call to perform the unlock.
3602
if not self._lock_token:
3603
raise AssertionError('Locked, but no token!')
3604
branch_token = self._lock_token
3605
repo_token = self._repo_lock_token
3606
self._lock_token = None
3607
self._repo_lock_token = None
3608
if not self._leave_lock:
3609
self._unlock(branch_token, repo_token)
3611
self.repository.unlock()
3613
def break_lock(self):
3615
response = self._call(
3616
'Branch.break_lock', self._remote_path())
3617
except errors.UnknownSmartMethod:
3619
return self._real_branch.break_lock()
3620
if response != ('ok',):
3621
raise errors.UnexpectedSmartServerResponse(response)
3623
def leave_lock_in_place(self):
3624
if not self._lock_token:
3625
raise NotImplementedError(self.leave_lock_in_place)
3626
self._leave_lock = True
3628
def dont_leave_lock_in_place(self):
3629
if not self._lock_token:
3630
raise NotImplementedError(self.dont_leave_lock_in_place)
3631
self._leave_lock = False
3634
def get_rev_id(self, revno, history=None):
3636
return _mod_revision.NULL_REVISION
3637
last_revision_info = self.last_revision_info()
3638
ok, result = self.repository.get_rev_id_for_revno(
3639
revno, last_revision_info)
3642
missing_parent = result[1]
3643
# Either the revision named by the server is missing, or its parent
3644
# is. Call get_parent_map to determine which, so that we report a
3646
parent_map = self.repository.get_parent_map([missing_parent])
3647
if missing_parent in parent_map:
3648
missing_parent = parent_map[missing_parent]
3649
raise errors.RevisionNotPresent(missing_parent, self.repository)
3651
def _read_last_revision_info(self):
3652
response = self._call('Branch.last_revision_info', self._remote_path())
3653
if response[0] != 'ok':
3654
raise SmartProtocolError('unexpected response code %s' % (response,))
3655
revno = int(response[1])
3656
last_revision = response[2]
3657
return (revno, last_revision)
3659
def _gen_revision_history(self):
3660
"""See Branch._gen_revision_history()."""
3661
if self._is_stacked:
3663
return self._real_branch._gen_revision_history()
3664
response_tuple, response_handler = self._call_expecting_body(
3665
'Branch.revision_history', self._remote_path())
3666
if response_tuple[0] != 'ok':
3667
raise errors.UnexpectedSmartServerResponse(response_tuple)
3668
result = response_handler.read_body_bytes().split('\x00')
3673
def _remote_path(self):
3674
return self.bzrdir._path_for_remote_call(self._client)
3676
def _set_last_revision_descendant(self, revision_id, other_branch,
3677
allow_diverged=False, allow_overwrite_descendant=False):
3678
# This performs additional work to meet the hook contract; while its
3679
# undesirable, we have to synthesise the revno to call the hook, and
3680
# not calling the hook is worse as it means changes can't be prevented.
3681
# Having calculated this though, we can't just call into
3682
# set_last_revision_info as a simple call, because there is a set_rh
3683
# hook that some folk may still be using.
3684
old_revno, old_revid = self.last_revision_info()
3685
history = self._lefthand_history(revision_id)
3686
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3687
err_context = {'other_branch': other_branch}
3688
response = self._call('Branch.set_last_revision_ex',
3689
self._remote_path(), self._lock_token, self._repo_lock_token,
3690
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3692
self._clear_cached_state()
3693
if len(response) != 3 and response[0] != 'ok':
3694
raise errors.UnexpectedSmartServerResponse(response)
3695
new_revno, new_revision_id = response[1:]
3696
self._last_revision_info_cache = new_revno, new_revision_id
3697
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3698
if self._real_branch is not None:
3699
cache = new_revno, new_revision_id
3700
self._real_branch._last_revision_info_cache = cache
3702
def _set_last_revision(self, revision_id):
3703
old_revno, old_revid = self.last_revision_info()
3704
# This performs additional work to meet the hook contract; while its
3705
# undesirable, we have to synthesise the revno to call the hook, and
3706
# not calling the hook is worse as it means changes can't be prevented.
3707
# Having calculated this though, we can't just call into
3708
# set_last_revision_info as a simple call, because there is a set_rh
3709
# hook that some folk may still be using.
3710
history = self._lefthand_history(revision_id)
3711
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3712
self._clear_cached_state()
3713
response = self._call('Branch.set_last_revision',
3714
self._remote_path(), self._lock_token, self._repo_lock_token,
3716
if response != ('ok',):
3717
raise errors.UnexpectedSmartServerResponse(response)
3718
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3720
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3722
def set_revision_history(self, rev_history):
3723
"""See Branch.set_revision_history."""
3724
self._set_revision_history(rev_history)
3727
def _set_revision_history(self, rev_history):
3728
# Send just the tip revision of the history; the server will generate
3729
# the full history from that. If the revision doesn't exist in this
3730
# branch, NoSuchRevision will be raised.
3731
if rev_history == []:
3734
rev_id = rev_history[-1]
3735
self._set_last_revision(rev_id)
3736
for hook in branch.Branch.hooks['set_rh']:
3737
hook(self, rev_history)
3738
self._cache_revision_history(rev_history)
3740
def _get_parent_location(self):
3741
medium = self._client._medium
3742
if medium._is_remote_before((1, 13)):
3743
return self._vfs_get_parent_location()
3745
response = self._call('Branch.get_parent', self._remote_path())
3746
except errors.UnknownSmartMethod:
3747
medium._remember_remote_is_before((1, 13))
3748
return self._vfs_get_parent_location()
3749
if len(response) != 1:
3750
raise errors.UnexpectedSmartServerResponse(response)
3751
parent_location = response[0]
3752
if parent_location == '':
3754
return parent_location
3756
def _vfs_get_parent_location(self):
3758
return self._real_branch._get_parent_location()
3760
def _set_parent_location(self, url):
3761
medium = self._client._medium
3762
if medium._is_remote_before((1, 15)):
3763
return self._vfs_set_parent_location(url)
3765
call_url = url or ''
3766
if type(call_url) is not str:
3767
raise AssertionError('url must be a str or None (%s)' % url)
3768
response = self._call('Branch.set_parent_location',
3769
self._remote_path(), self._lock_token, self._repo_lock_token,
3771
except errors.UnknownSmartMethod:
3772
medium._remember_remote_is_before((1, 15))
3773
return self._vfs_set_parent_location(url)
3775
raise errors.UnexpectedSmartServerResponse(response)
3777
def _vfs_set_parent_location(self, url):
3779
return self._real_branch._set_parent_location(url)
3782
def pull(self, source, overwrite=False, stop_revision=None,
3784
self._clear_cached_state_of_remote_branch_only()
3786
return self._real_branch.pull(
3787
source, overwrite=overwrite, stop_revision=stop_revision,
3788
_override_hook_target=self, **kwargs)
3791
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3793
return self._real_branch.push(
3794
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3795
_override_hook_source_branch=self)
3797
def is_locked(self):
3798
return self._lock_count >= 1
3801
def revision_id_to_dotted_revno(self, revision_id):
3802
"""Given a revision id, return its dotted revno.
3804
:return: a tuple like (1,) or (400,1,3).
3807
response = self._call('Branch.revision_id_to_revno',
3808
self._remote_path(), revision_id)
3809
except errors.UnknownSmartMethod:
3811
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3812
if response[0] == 'ok':
3813
return tuple([int(x) for x in response[1:]])
3815
raise errors.UnexpectedSmartServerResponse(response)
3818
def revision_id_to_revno(self, revision_id):
3819
"""Given a revision id on the branch mainline, return its revno.
3824
response = self._call('Branch.revision_id_to_revno',
3825
self._remote_path(), revision_id)
3826
except errors.UnknownSmartMethod:
3828
return self._real_branch.revision_id_to_revno(revision_id)
3829
if response[0] == 'ok':
3830
if len(response) == 2:
3831
return int(response[1])
3832
raise NoSuchRevision(self, revision_id)
3834
raise errors.UnexpectedSmartServerResponse(response)
3837
def set_last_revision_info(self, revno, revision_id):
3838
# XXX: These should be returned by the set_last_revision_info verb
3839
old_revno, old_revid = self.last_revision_info()
3840
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3841
if not revision_id or not isinstance(revision_id, basestring):
3842
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3844
response = self._call('Branch.set_last_revision_info',
3845
self._remote_path(), self._lock_token, self._repo_lock_token,
3846
str(revno), revision_id)
3847
except errors.UnknownSmartMethod:
3849
self._clear_cached_state_of_remote_branch_only()
3850
self._real_branch.set_last_revision_info(revno, revision_id)
3851
self._last_revision_info_cache = revno, revision_id
3853
if response == ('ok',):
3854
self._clear_cached_state()
3855
self._last_revision_info_cache = revno, revision_id
3856
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3857
# Update the _real_branch's cache too.
3858
if self._real_branch is not None:
3859
cache = self._last_revision_info_cache
3860
self._real_branch._last_revision_info_cache = cache
3862
raise errors.UnexpectedSmartServerResponse(response)
3865
def generate_revision_history(self, revision_id, last_rev=None,
3867
medium = self._client._medium
3868
if not medium._is_remote_before((1, 6)):
3869
# Use a smart method for 1.6 and above servers
3871
self._set_last_revision_descendant(revision_id, other_branch,
3872
allow_diverged=True, allow_overwrite_descendant=True)
3874
except errors.UnknownSmartMethod:
3875
medium._remember_remote_is_before((1, 6))
3876
self._clear_cached_state_of_remote_branch_only()
3877
self._set_revision_history(self._lefthand_history(revision_id,
3878
last_rev=last_rev,other_branch=other_branch))
3880
def set_push_location(self, location):
3882
return self._real_branch.set_push_location(location)
3884
def heads_to_fetch(self):
3885
if self._format._use_default_local_heads_to_fetch():
3886
# We recognise this format, and its heads-to-fetch implementation
3887
# is the default one (tip + tags). In this case it's cheaper to
3888
# just use the default implementation rather than a special RPC as
3889
# the tip and tags data is cached.
3890
return branch.Branch.heads_to_fetch(self)
3891
medium = self._client._medium
3892
if medium._is_remote_before((2, 4)):
3893
return self._vfs_heads_to_fetch()
3895
return self._rpc_heads_to_fetch()
3896
except errors.UnknownSmartMethod:
3897
medium._remember_remote_is_before((2, 4))
3898
return self._vfs_heads_to_fetch()
3900
def _rpc_heads_to_fetch(self):
3901
response = self._call('Branch.heads_to_fetch', self._remote_path())
3902
if len(response) != 2:
3903
raise errors.UnexpectedSmartServerResponse(response)
3904
must_fetch, if_present_fetch = response
3905
return set(must_fetch), set(if_present_fetch)
3907
def _vfs_heads_to_fetch(self):
3909
return self._real_branch.heads_to_fetch()
3912
class RemoteConfig(object):
3913
"""A Config that reads and writes from smart verbs.
3915
It is a low-level object that considers config data to be name/value pairs
3916
that may be associated with a section. Assigning meaning to the these
3917
values is done at higher levels like bzrlib.config.TreeConfig.
3920
def get_option(self, name, section=None, default=None):
3921
"""Return the value associated with a named option.
3923
:param name: The name of the value
3924
:param section: The section the option is in (if any)
3925
:param default: The value to return if the value is not set
3926
:return: The value or default value
3929
configobj = self._get_configobj()
3932
section_obj = configobj
3935
section_obj = configobj[section]
3938
if section_obj is None:
3941
value = section_obj.get(name, default)
3942
except errors.UnknownSmartMethod:
3943
value = self._vfs_get_option(name, section, default)
3944
for hook in config.OldConfigHooks['get']:
3945
hook(self, name, value)
3948
def _response_to_configobj(self, response):
3949
if len(response[0]) and response[0][0] != 'ok':
3950
raise errors.UnexpectedSmartServerResponse(response)
3951
lines = response[1].read_body_bytes().splitlines()
3952
conf = config.ConfigObj(lines, encoding='utf-8')
3953
for hook in config.OldConfigHooks['load']:
3958
class RemoteBranchConfig(RemoteConfig):
3959
"""A RemoteConfig for Branches."""
3961
def __init__(self, branch):
3962
self._branch = branch
3964
def _get_configobj(self):
3965
path = self._branch._remote_path()
3966
response = self._branch._client.call_expecting_body(
3967
'Branch.get_config_file', path)
3968
return self._response_to_configobj(response)
3970
def set_option(self, value, name, section=None):
3971
"""Set the value associated with a named option.
3973
:param value: The value to set
3974
:param name: The name of the value to set
3975
:param section: The section the option is in (if any)
3977
medium = self._branch._client._medium
3978
if medium._is_remote_before((1, 14)):
3979
return self._vfs_set_option(value, name, section)
3980
if isinstance(value, dict):
3981
if medium._is_remote_before((2, 2)):
3982
return self._vfs_set_option(value, name, section)
3983
return self._set_config_option_dict(value, name, section)
3985
return self._set_config_option(value, name, section)
3987
def _set_config_option(self, value, name, section):
3989
path = self._branch._remote_path()
3990
response = self._branch._client.call('Branch.set_config_option',
3991
path, self._branch._lock_token, self._branch._repo_lock_token,
3992
value.encode('utf8'), name, section or '')
3993
except errors.UnknownSmartMethod:
3994
medium = self._branch._client._medium
3995
medium._remember_remote_is_before((1, 14))
3996
return self._vfs_set_option(value, name, section)
3998
raise errors.UnexpectedSmartServerResponse(response)
4000
def _serialize_option_dict(self, option_dict):
4002
for key, value in option_dict.items():
4003
if isinstance(key, unicode):
4004
key = key.encode('utf8')
4005
if isinstance(value, unicode):
4006
value = value.encode('utf8')
4007
utf8_dict[key] = value
4008
return bencode.bencode(utf8_dict)
4010
def _set_config_option_dict(self, value, name, section):
4012
path = self._branch._remote_path()
4013
serialised_dict = self._serialize_option_dict(value)
4014
response = self._branch._client.call(
4015
'Branch.set_config_option_dict',
4016
path, self._branch._lock_token, self._branch._repo_lock_token,
4017
serialised_dict, name, section or '')
4018
except errors.UnknownSmartMethod:
4019
medium = self._branch._client._medium
4020
medium._remember_remote_is_before((2, 2))
4021
return self._vfs_set_option(value, name, section)
4023
raise errors.UnexpectedSmartServerResponse(response)
4025
def _real_object(self):
4026
self._branch._ensure_real()
4027
return self._branch._real_branch
4029
def _vfs_set_option(self, value, name, section=None):
4030
return self._real_object()._get_config().set_option(
4031
value, name, section)
4034
class RemoteBzrDirConfig(RemoteConfig):
4035
"""A RemoteConfig for BzrDirs."""
4037
def __init__(self, bzrdir):
4038
self._bzrdir = bzrdir
4040
def _get_configobj(self):
4041
medium = self._bzrdir._client._medium
4042
verb = 'BzrDir.get_config_file'
4043
if medium._is_remote_before((1, 15)):
4044
raise errors.UnknownSmartMethod(verb)
4045
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4046
response = self._bzrdir._call_expecting_body(
4048
return self._response_to_configobj(response)
4050
def _vfs_get_option(self, name, section, default):
4051
return self._real_object()._get_config().get_option(
4052
name, section, default)
4054
def set_option(self, value, name, section=None):
4055
"""Set the value associated with a named option.
4057
:param value: The value to set
4058
:param name: The name of the value to set
4059
:param section: The section the option is in (if any)
4061
return self._real_object()._get_config().set_option(
4062
value, name, section)
4064
def _real_object(self):
4065
self._bzrdir._ensure_real()
4066
return self._bzrdir._real_bzrdir
4069
def _extract_tar(tar, to_dir):
4070
"""Extract all the contents of a tarfile object.
4072
A replacement for extractall, which is not present in python2.4
4075
tar.extract(tarinfo, to_dir)
4078
error_translators = registry.Registry()
4079
no_context_error_translators = registry.Registry()
4082
def _translate_error(err, **context):
4083
"""Translate an ErrorFromSmartServer into a more useful error.
4085
Possible context keys:
4093
If the error from the server doesn't match a known pattern, then
4094
UnknownErrorFromSmartServer is raised.
4098
return context[name]
4099
except KeyError, key_err:
4100
mutter('Missing key %r in context %r', key_err.args[0], context)
4103
"""Get the path from the context if present, otherwise use first error
4107
return context['path']
4108
except KeyError, key_err:
4110
return err.error_args[0]
4111
except IndexError, idx_err:
4113
'Missing key %r in context %r', key_err.args[0], context)
4117
translator = error_translators.get(err.error_verb)
4121
raise translator(err, find, get_path)
4123
translator = no_context_error_translators.get(err.error_verb)
4125
raise errors.UnknownErrorFromSmartServer(err)
4127
raise translator(err)
4130
error_translators.register('NoSuchRevision',
4131
lambda err, find, get_path: NoSuchRevision(
4132
find('branch'), err.error_args[0]))
4133
error_translators.register('nosuchrevision',
4134
lambda err, find, get_path: NoSuchRevision(
4135
find('repository'), err.error_args[0]))
4137
def _translate_nobranch_error(err, find, get_path):
4138
if len(err.error_args) >= 1:
4139
extra = err.error_args[0]
4142
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4145
error_translators.register('nobranch', _translate_nobranch_error)
4146
error_translators.register('norepository',
4147
lambda err, find, get_path: errors.NoRepositoryPresent(
4149
error_translators.register('UnlockableTransport',
4150
lambda err, find, get_path: errors.UnlockableTransport(
4151
find('bzrdir').root_transport))
4152
error_translators.register('TokenMismatch',
4153
lambda err, find, get_path: errors.TokenMismatch(
4154
find('token'), '(remote token)'))
4155
error_translators.register('Diverged',
4156
lambda err, find, get_path: errors.DivergedBranches(
4157
find('branch'), find('other_branch')))
4158
error_translators.register('NotStacked',
4159
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4161
def _translate_PermissionDenied(err, find, get_path):
4163
if len(err.error_args) >= 2:
4164
extra = err.error_args[1]
4167
return errors.PermissionDenied(path, extra=extra)
4169
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4170
error_translators.register('ReadError',
4171
lambda err, find, get_path: errors.ReadError(get_path()))
4172
error_translators.register('NoSuchFile',
4173
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4174
error_translators.register('TokenLockingNotSupported',
4175
lambda err, find, get_path: errors.TokenLockingNotSupported(
4176
find('repository')))
4177
error_translators.register('UnsuspendableWriteGroup',
4178
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4179
repository=find('repository')))
4180
error_translators.register('UnresumableWriteGroup',
4181
lambda err, find, get_path: errors.UnresumableWriteGroup(
4182
repository=find('repository'), write_groups=err.error_args[0],
4183
reason=err.error_args[1]))
4184
no_context_error_translators.register('IncompatibleRepositories',
4185
lambda err: errors.IncompatibleRepositories(
4186
err.error_args[0], err.error_args[1], err.error_args[2]))
4187
no_context_error_translators.register('LockContention',
4188
lambda err: errors.LockContention('(remote lock)'))
4189
no_context_error_translators.register('LockFailed',
4190
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4191
no_context_error_translators.register('TipChangeRejected',
4192
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4193
no_context_error_translators.register('UnstackableBranchFormat',
4194
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4195
no_context_error_translators.register('UnstackableRepositoryFormat',
4196
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4197
no_context_error_translators.register('FileExists',
4198
lambda err: errors.FileExists(err.error_args[0]))
4199
no_context_error_translators.register('DirectoryNotEmpty',
4200
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4202
def _translate_short_readv_error(err):
4203
args = err.error_args
4204
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4207
no_context_error_translators.register('ShortReadvError',
4208
_translate_short_readv_error)
4210
def _translate_unicode_error(err):
4211
encoding = str(err.error_args[0]) # encoding must always be a string
4212
val = err.error_args[1]
4213
start = int(err.error_args[2])
4214
end = int(err.error_args[3])
4215
reason = str(err.error_args[4]) # reason must always be a string
4216
if val.startswith('u:'):
4217
val = val[2:].decode('utf-8')
4218
elif val.startswith('s:'):
4219
val = val[2:].decode('base64')
4220
if err.error_verb == 'UnicodeDecodeError':
4221
raise UnicodeDecodeError(encoding, val, start, end, reason)
4222
elif err.error_verb == 'UnicodeEncodeError':
4223
raise UnicodeEncodeError(encoding, val, start, end, reason)
4225
no_context_error_translators.register('UnicodeEncodeError',
4226
_translate_unicode_error)
4227
no_context_error_translators.register('UnicodeDecodeError',
4228
_translate_unicode_error)
4229
no_context_error_translators.register('ReadOnlyError',
4230
lambda err: errors.TransportNotPossible('readonly transport'))
4231
no_context_error_translators.register('MemoryError',
4232
lambda err: errors.BzrError("remote server out of memory\n"
4233
"Retry non-remotely, or contact the server admin for details."))
4234
no_context_error_translators.register('RevisionNotPresent',
4235
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4237
no_context_error_translators.register('BzrCheckError',
4238
lambda err: errors.BzrCheckError(msg=err.error_args[0]))