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
1215
def resume_write_group(self, tokens):
1216
if self._real_repository:
1217
return self._real_repository.resume_write_group(tokens)
1218
path = self.bzrdir._path_for_remote_call(self._client)
1220
response = self._call('Repository.check_write_group', path,
1221
self._lock_token, tokens)
1222
except errors.UnknownSmartMethod:
1224
return self._real_repository.resume_write_group(tokens)
1225
if response != ('ok', ):
1226
raise errors.UnexpectedSmartServerResponse(response)
1227
self._write_group_tokens = tokens
1229
def suspend_write_group(self):
1230
if self._real_repository:
1231
return self._real_repository.suspend_write_group()
1232
ret = self._write_group_tokens or []
1233
self._write_group_tokens = None
1236
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1238
return self._real_repository.get_missing_parent_inventories(
1239
check_for_missing_texts=check_for_missing_texts)
1241
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1243
return self._real_repository.get_rev_id_for_revno(
1246
def get_rev_id_for_revno(self, revno, known_pair):
1247
"""See Repository.get_rev_id_for_revno."""
1248
path = self.bzrdir._path_for_remote_call(self._client)
1250
if self._client._medium._is_remote_before((1, 17)):
1251
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1252
response = self._call(
1253
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1254
except errors.UnknownSmartMethod:
1255
self._client._medium._remember_remote_is_before((1, 17))
1256
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1257
if response[0] == 'ok':
1258
return True, response[1]
1259
elif response[0] == 'history-incomplete':
1260
known_pair = response[1:3]
1261
for fallback in self._fallback_repositories:
1262
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1267
# Not found in any fallbacks
1268
return False, known_pair
1270
raise errors.UnexpectedSmartServerResponse(response)
1272
def _ensure_real(self):
1273
"""Ensure that there is a _real_repository set.
1275
Used before calls to self._real_repository.
1277
Note that _ensure_real causes many roundtrips to the server which are
1278
not desirable, and prevents the use of smart one-roundtrip RPC's to
1279
perform complex operations (such as accessing parent data, streaming
1280
revisions etc). Adding calls to _ensure_real should only be done when
1281
bringing up new functionality, adding fallbacks for smart methods that
1282
require a fallback path, and never to replace an existing smart method
1283
invocation. If in doubt chat to the bzr network team.
1285
if self._real_repository is None:
1286
if 'hpssvfs' in debug.debug_flags:
1288
warning('VFS Repository access triggered\n%s',
1289
''.join(traceback.format_stack()))
1290
self._unstacked_provider.missing_keys.clear()
1291
self.bzrdir._ensure_real()
1292
self._set_real_repository(
1293
self.bzrdir._real_bzrdir.open_repository())
1295
def _translate_error(self, err, **context):
1296
self.bzrdir._translate_error(err, repository=self, **context)
1298
def find_text_key_references(self):
1299
"""Find the text key references within the repository.
1301
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1302
to whether they were referred to by the inventory of the
1303
revision_id that they contain. The inventory texts from all present
1304
revision ids are assessed to generate this report.
1307
return self._real_repository.find_text_key_references()
1309
def _generate_text_key_index(self):
1310
"""Generate a new text key index for the repository.
1312
This is an expensive function that will take considerable time to run.
1314
:return: A dict mapping (file_id, revision_id) tuples to a list of
1315
parents, also (file_id, revision_id) tuples.
1318
return self._real_repository._generate_text_key_index()
1320
def _get_revision_graph(self, revision_id):
1321
"""Private method for using with old (< 1.2) servers to fallback."""
1322
if revision_id is None:
1324
elif _mod_revision.is_null(revision_id):
1327
path = self.bzrdir._path_for_remote_call(self._client)
1328
response = self._call_expecting_body(
1329
'Repository.get_revision_graph', path, revision_id)
1330
response_tuple, response_handler = response
1331
if response_tuple[0] != 'ok':
1332
raise errors.UnexpectedSmartServerResponse(response_tuple)
1333
coded = response_handler.read_body_bytes()
1335
# no revisions in this repository!
1337
lines = coded.split('\n')
1340
d = tuple(line.split())
1341
revision_graph[d[0]] = d[1:]
1343
return revision_graph
1345
def _get_sink(self):
1346
"""See Repository._get_sink()."""
1347
return RemoteStreamSink(self)
1349
def _get_source(self, to_format):
1350
"""Return a source for streaming from this repository."""
1351
return RemoteStreamSource(self, to_format)
1354
def get_file_graph(self):
1355
return graph.Graph(self.texts)
1358
def has_revision(self, revision_id):
1359
"""True if this repository has a copy of the revision."""
1360
# Copy of bzrlib.repository.Repository.has_revision
1361
return revision_id in self.has_revisions((revision_id,))
1364
def has_revisions(self, revision_ids):
1365
"""Probe to find out the presence of multiple revisions.
1367
:param revision_ids: An iterable of revision_ids.
1368
:return: A set of the revision_ids that were present.
1370
# Copy of bzrlib.repository.Repository.has_revisions
1371
parent_map = self.get_parent_map(revision_ids)
1372
result = set(parent_map)
1373
if _mod_revision.NULL_REVISION in revision_ids:
1374
result.add(_mod_revision.NULL_REVISION)
1377
def _has_same_fallbacks(self, other_repo):
1378
"""Returns true if the repositories have the same fallbacks."""
1379
# XXX: copied from Repository; it should be unified into a base class
1380
# <https://bugs.launchpad.net/bzr/+bug/401622>
1381
my_fb = self._fallback_repositories
1382
other_fb = other_repo._fallback_repositories
1383
if len(my_fb) != len(other_fb):
1385
for f, g in zip(my_fb, other_fb):
1386
if not f.has_same_location(g):
1390
def has_same_location(self, other):
1391
# TODO: Move to RepositoryBase and unify with the regular Repository
1392
# one; unfortunately the tests rely on slightly different behaviour at
1393
# present -- mbp 20090710
1394
return (self.__class__ is other.__class__ and
1395
self.bzrdir.transport.base == other.bzrdir.transport.base)
1397
def get_graph(self, other_repository=None):
1398
"""Return the graph for this repository format"""
1399
parents_provider = self._make_parents_provider(other_repository)
1400
return graph.Graph(parents_provider)
1403
def get_known_graph_ancestry(self, revision_ids):
1404
"""Return the known graph for a set of revision ids and their ancestors.
1406
st = static_tuple.StaticTuple
1407
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1408
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1409
return graph.GraphThunkIdsToKeys(known_graph)
1411
def gather_stats(self, revid=None, committers=None):
1412
"""See Repository.gather_stats()."""
1413
path = self.bzrdir._path_for_remote_call(self._client)
1414
# revid can be None to indicate no revisions, not just NULL_REVISION
1415
if revid is None or _mod_revision.is_null(revid):
1419
if committers is None or not committers:
1420
fmt_committers = 'no'
1422
fmt_committers = 'yes'
1423
response_tuple, response_handler = self._call_expecting_body(
1424
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1425
if response_tuple[0] != 'ok':
1426
raise errors.UnexpectedSmartServerResponse(response_tuple)
1428
body = response_handler.read_body_bytes()
1430
for line in body.split('\n'):
1433
key, val_text = line.split(':')
1434
if key in ('revisions', 'size', 'committers'):
1435
result[key] = int(val_text)
1436
elif key in ('firstrev', 'latestrev'):
1437
values = val_text.split(' ')[1:]
1438
result[key] = (float(values[0]), long(values[1]))
1442
def find_branches(self, using=False):
1443
"""See Repository.find_branches()."""
1444
# should be an API call to the server.
1446
return self._real_repository.find_branches(using=using)
1448
def get_physical_lock_status(self):
1449
"""See Repository.get_physical_lock_status()."""
1450
path = self.bzrdir._path_for_remote_call(self._client)
1452
response = self._call('Repository.get_physical_lock_status', path)
1453
except errors.UnknownSmartMethod:
1455
return self._real_repository.get_physical_lock_status()
1456
if response[0] not in ('yes', 'no'):
1457
raise errors.UnexpectedSmartServerResponse(response)
1458
return (response[0] == 'yes')
1460
def is_in_write_group(self):
1461
"""Return True if there is an open write group.
1463
write groups are only applicable locally for the smart server..
1465
if self._write_group_tokens is not None:
1467
if self._real_repository:
1468
return self._real_repository.is_in_write_group()
1470
def is_locked(self):
1471
return self._lock_count >= 1
1473
def is_shared(self):
1474
"""See Repository.is_shared()."""
1475
path = self.bzrdir._path_for_remote_call(self._client)
1476
response = self._call('Repository.is_shared', path)
1477
if response[0] not in ('yes', 'no'):
1478
raise SmartProtocolError('unexpected response code %s' % (response,))
1479
return response[0] == 'yes'
1481
def is_write_locked(self):
1482
return self._lock_mode == 'w'
1484
def _warn_if_deprecated(self, branch=None):
1485
# If we have a real repository, the check will be done there, if we
1486
# don't the check will be done remotely.
1489
def lock_read(self):
1490
"""Lock the repository for read operations.
1492
:return: A bzrlib.lock.LogicalLockResult.
1494
# wrong eventually - want a local lock cache context
1495
if not self._lock_mode:
1496
self._note_lock('r')
1497
self._lock_mode = 'r'
1498
self._lock_count = 1
1499
self._unstacked_provider.enable_cache(cache_misses=True)
1500
if self._real_repository is not None:
1501
self._real_repository.lock_read()
1502
for repo in self._fallback_repositories:
1505
self._lock_count += 1
1506
return lock.LogicalLockResult(self.unlock)
1508
def _remote_lock_write(self, token):
1509
path = self.bzrdir._path_for_remote_call(self._client)
1512
err_context = {'token': token}
1513
response = self._call('Repository.lock_write', path, token,
1515
if response[0] == 'ok':
1516
ok, token = response
1519
raise errors.UnexpectedSmartServerResponse(response)
1521
def lock_write(self, token=None, _skip_rpc=False):
1522
if not self._lock_mode:
1523
self._note_lock('w')
1525
if self._lock_token is not None:
1526
if token != self._lock_token:
1527
raise errors.TokenMismatch(token, self._lock_token)
1528
self._lock_token = token
1530
self._lock_token = self._remote_lock_write(token)
1531
# if self._lock_token is None, then this is something like packs or
1532
# svn where we don't get to lock the repo, or a weave style repository
1533
# where we cannot lock it over the wire and attempts to do so will
1535
if self._real_repository is not None:
1536
self._real_repository.lock_write(token=self._lock_token)
1537
if token is not None:
1538
self._leave_lock = True
1540
self._leave_lock = False
1541
self._lock_mode = 'w'
1542
self._lock_count = 1
1543
cache_misses = self._real_repository is None
1544
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1545
for repo in self._fallback_repositories:
1546
# Writes don't affect fallback repos
1548
elif self._lock_mode == 'r':
1549
raise errors.ReadOnlyError(self)
1551
self._lock_count += 1
1552
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1554
def leave_lock_in_place(self):
1555
if not self._lock_token:
1556
raise NotImplementedError(self.leave_lock_in_place)
1557
self._leave_lock = True
1559
def dont_leave_lock_in_place(self):
1560
if not self._lock_token:
1561
raise NotImplementedError(self.dont_leave_lock_in_place)
1562
self._leave_lock = False
1564
def _set_real_repository(self, repository):
1565
"""Set the _real_repository for this repository.
1567
:param repository: The repository to fallback to for non-hpss
1568
implemented operations.
1570
if self._real_repository is not None:
1571
# Replacing an already set real repository.
1572
# We cannot do this [currently] if the repository is locked -
1573
# synchronised state might be lost.
1574
if self.is_locked():
1575
raise AssertionError('_real_repository is already set')
1576
if isinstance(repository, RemoteRepository):
1577
raise AssertionError()
1578
self._real_repository = repository
1579
# three code paths happen here:
1580
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1581
# up stacking. In this case self._fallback_repositories is [], and the
1582
# real repo is already setup. Preserve the real repo and
1583
# RemoteRepository.add_fallback_repository will avoid adding
1585
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1586
# ensure_real is triggered from a branch, the real repository to
1587
# set already has a matching list with separate instances, but
1588
# as they are also RemoteRepositories we don't worry about making the
1589
# lists be identical.
1590
# 3) new servers, RemoteRepository.ensure_real is triggered before
1591
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1592
# and need to populate it.
1593
if (self._fallback_repositories and
1594
len(self._real_repository._fallback_repositories) !=
1595
len(self._fallback_repositories)):
1596
if len(self._real_repository._fallback_repositories):
1597
raise AssertionError(
1598
"cannot cleanly remove existing _fallback_repositories")
1599
for fb in self._fallback_repositories:
1600
self._real_repository.add_fallback_repository(fb)
1601
if self._lock_mode == 'w':
1602
# if we are already locked, the real repository must be able to
1603
# acquire the lock with our token.
1604
self._real_repository.lock_write(self._lock_token)
1605
elif self._lock_mode == 'r':
1606
self._real_repository.lock_read()
1607
if self._write_group_tokens is not None:
1608
# if we are already in a write group, resume it
1609
self._real_repository.resume_write_group(self._write_group_tokens)
1610
self._write_group_tokens = None
1612
def start_write_group(self):
1613
"""Start a write group on the decorated repository.
1615
Smart methods perform operations in a single step so this API
1616
is not really applicable except as a compatibility thunk
1617
for older plugins that don't use e.g. the CommitBuilder
1620
if self._real_repository:
1622
return self._real_repository.start_write_group()
1623
if not self.is_write_locked():
1624
raise errors.NotWriteLocked(self)
1625
if self._write_group_tokens is not None:
1626
raise errors.BzrError('already in a write group')
1627
path = self.bzrdir._path_for_remote_call(self._client)
1629
response = self._call('Repository.start_write_group', path,
1631
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1633
return self._real_repository.start_write_group()
1634
if response[0] != 'ok':
1635
raise errors.UnexpectedSmartServerResponse(response)
1636
self._write_group_tokens = response[1]
1638
def _unlock(self, token):
1639
path = self.bzrdir._path_for_remote_call(self._client)
1641
# with no token the remote repository is not persistently locked.
1643
err_context = {'token': token}
1644
response = self._call('Repository.unlock', path, token,
1646
if response == ('ok',):
1649
raise errors.UnexpectedSmartServerResponse(response)
1651
@only_raises(errors.LockNotHeld, errors.LockBroken)
1653
if not self._lock_count:
1654
return lock.cant_unlock_not_held(self)
1655
self._lock_count -= 1
1656
if self._lock_count > 0:
1658
self._unstacked_provider.disable_cache()
1659
old_mode = self._lock_mode
1660
self._lock_mode = None
1662
# The real repository is responsible at present for raising an
1663
# exception if it's in an unfinished write group. However, it
1664
# normally will *not* actually remove the lock from disk - that's
1665
# done by the server on receiving the Repository.unlock call.
1666
# This is just to let the _real_repository stay up to date.
1667
if self._real_repository is not None:
1668
self._real_repository.unlock()
1669
elif self._write_group_tokens is not None:
1670
self.abort_write_group()
1672
# The rpc-level lock should be released even if there was a
1673
# problem releasing the vfs-based lock.
1675
# Only write-locked repositories need to make a remote method
1676
# call to perform the unlock.
1677
old_token = self._lock_token
1678
self._lock_token = None
1679
if not self._leave_lock:
1680
self._unlock(old_token)
1681
# Fallbacks are always 'lock_read()' so we don't pay attention to
1683
for repo in self._fallback_repositories:
1686
def break_lock(self):
1687
# should hand off to the network
1688
path = self.bzrdir._path_for_remote_call(self._client)
1690
response = self._call("Repository.break_lock", path)
1691
except errors.UnknownSmartMethod:
1693
return self._real_repository.break_lock()
1694
if response != ('ok',):
1695
raise errors.UnexpectedSmartServerResponse(response)
1697
def _get_tarball(self, compression):
1698
"""Return a TemporaryFile containing a repository tarball.
1700
Returns None if the server does not support sending tarballs.
1703
path = self.bzrdir._path_for_remote_call(self._client)
1705
response, protocol = self._call_expecting_body(
1706
'Repository.tarball', path, compression)
1707
except errors.UnknownSmartMethod:
1708
protocol.cancel_read_body()
1710
if response[0] == 'ok':
1711
# Extract the tarball and return it
1712
t = tempfile.NamedTemporaryFile()
1713
# TODO: rpc layer should read directly into it...
1714
t.write(protocol.read_body_bytes())
1717
raise errors.UnexpectedSmartServerResponse(response)
1720
def sprout(self, to_bzrdir, revision_id=None):
1721
"""Create a descendent repository for new development.
1723
Unlike clone, this does not copy the settings of the repository.
1725
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1726
dest_repo.fetch(self, revision_id=revision_id)
1729
def _create_sprouting_repo(self, a_bzrdir, shared):
1730
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1731
# use target default format.
1732
dest_repo = a_bzrdir.create_repository()
1734
# Most control formats need the repository to be specifically
1735
# created, but on some old all-in-one formats it's not needed
1737
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1738
except errors.UninitializableFormat:
1739
dest_repo = a_bzrdir.open_repository()
1742
### These methods are just thin shims to the VFS object for now.
1745
def revision_tree(self, revision_id):
1746
revision_id = _mod_revision.ensure_null(revision_id)
1747
if revision_id == _mod_revision.NULL_REVISION:
1748
return InventoryRevisionTree(self,
1749
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1751
return list(self.revision_trees([revision_id]))[0]
1753
def get_serializer_format(self):
1754
path = self.bzrdir._path_for_remote_call(self._client)
1756
response = self._call('VersionedFileRepository.get_serializer_format',
1758
except errors.UnknownSmartMethod:
1760
return self._real_repository.get_serializer_format()
1761
if response[0] != 'ok':
1762
raise errors.UnexpectedSmartServerResponse(response)
1765
def get_commit_builder(self, branch, parents, config, timestamp=None,
1766
timezone=None, committer=None, revprops=None,
1767
revision_id=None, lossy=False):
1768
# FIXME: It ought to be possible to call this without immediately
1769
# triggering _ensure_real. For now it's the easiest thing to do.
1771
real_repo = self._real_repository
1772
builder = real_repo.get_commit_builder(branch, parents,
1773
config, timestamp=timestamp, timezone=timezone,
1774
committer=committer, revprops=revprops,
1775
revision_id=revision_id, lossy=lossy)
1778
def add_fallback_repository(self, repository):
1779
"""Add a repository to use for looking up data not held locally.
1781
:param repository: A repository.
1783
if not self._format.supports_external_lookups:
1784
raise errors.UnstackableRepositoryFormat(
1785
self._format.network_name(), self.base)
1786
# We need to accumulate additional repositories here, to pass them in
1789
# Make the check before we lock: this raises an exception.
1790
self._check_fallback_repository(repository)
1791
if self.is_locked():
1792
# We will call fallback.unlock() when we transition to the unlocked
1793
# state, so always add a lock here. If a caller passes us a locked
1794
# repository, they are responsible for unlocking it later.
1795
repository.lock_read()
1796
self._fallback_repositories.append(repository)
1797
# If self._real_repository was parameterised already (e.g. because a
1798
# _real_branch had its get_stacked_on_url method called), then the
1799
# repository to be added may already be in the _real_repositories list.
1800
if self._real_repository is not None:
1801
fallback_locations = [repo.user_url for repo in
1802
self._real_repository._fallback_repositories]
1803
if repository.user_url not in fallback_locations:
1804
self._real_repository.add_fallback_repository(repository)
1806
def _check_fallback_repository(self, repository):
1807
"""Check that this repository can fallback to repository safely.
1809
Raise an error if not.
1811
:param repository: A repository to fallback to.
1813
return _mod_repository.InterRepository._assert_same_model(
1816
def add_inventory(self, revid, inv, parents):
1818
return self._real_repository.add_inventory(revid, inv, parents)
1820
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1821
parents, basis_inv=None, propagate_caches=False):
1823
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1824
delta, new_revision_id, parents, basis_inv=basis_inv,
1825
propagate_caches=propagate_caches)
1827
def add_revision(self, rev_id, rev, inv=None, config=None):
1829
return self._real_repository.add_revision(
1830
rev_id, rev, inv=inv, config=config)
1833
def get_inventory(self, revision_id):
1834
return list(self.iter_inventories([revision_id]))[0]
1836
def _iter_inventories_rpc(self, revision_ids, ordering):
1837
if ordering is None:
1838
ordering = 'unordered'
1839
path = self.bzrdir._path_for_remote_call(self._client)
1840
body = "\n".join(revision_ids)
1841
response_tuple, response_handler = (
1842
self._call_with_body_bytes_expecting_body(
1843
"VersionedFileRepository.get_inventories",
1844
(path, ordering), body))
1845
if response_tuple[0] != "ok":
1846
raise errors.UnexpectedSmartServerResponse(response_tuple)
1847
deserializer = inventory_delta.InventoryDeltaDeserializer()
1848
byte_stream = response_handler.read_streamed_body()
1849
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1851
# no results whatsoever
1853
src_format, stream = decoded
1854
if src_format.network_name() != self._format.network_name():
1855
raise AssertionError(
1856
"Mismatched RemoteRepository and stream src %r, %r" % (
1857
src_format.network_name(), self._format.network_name()))
1858
# ignore the src format, it's not really relevant
1859
prev_inv = Inventory(root_id=None,
1860
revision_id=_mod_revision.NULL_REVISION)
1861
# there should be just one substream, with inventory deltas
1862
substream_kind, substream = stream.next()
1863
if substream_kind != "inventory-deltas":
1864
raise AssertionError(
1865
"Unexpected stream %r received" % substream_kind)
1866
for record in substream:
1867
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1868
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1869
if parent_id != prev_inv.revision_id:
1870
raise AssertionError("invalid base %r != %r" % (parent_id,
1871
prev_inv.revision_id))
1872
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1873
yield inv, inv.revision_id
1876
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1878
return self._real_repository._iter_inventories(revision_ids, ordering)
1880
def iter_inventories(self, revision_ids, ordering=None):
1881
"""Get many inventories by revision_ids.
1883
This will buffer some or all of the texts used in constructing the
1884
inventories in memory, but will only parse a single inventory at a
1887
:param revision_ids: The expected revision ids of the inventories.
1888
:param ordering: optional ordering, e.g. 'topological'. If not
1889
specified, the order of revision_ids will be preserved (by
1890
buffering if necessary).
1891
:return: An iterator of inventories.
1893
if ((None in revision_ids)
1894
or (_mod_revision.NULL_REVISION in revision_ids)):
1895
raise ValueError('cannot get null revision inventory')
1896
for inv, revid in self._iter_inventories(revision_ids, ordering):
1898
raise errors.NoSuchRevision(self, revid)
1901
def _iter_inventories(self, revision_ids, ordering=None):
1902
if len(revision_ids) == 0:
1904
missing = set(revision_ids)
1905
if ordering is None:
1906
order_as_requested = True
1908
order = list(revision_ids)
1910
next_revid = order.pop()
1912
order_as_requested = False
1913
if ordering != 'unordered' and self._fallback_repositories:
1914
raise ValueError('unsupported ordering %r' % ordering)
1915
iter_inv_fns = [self._iter_inventories_rpc] + [
1916
fallback._iter_inventories for fallback in
1917
self._fallback_repositories]
1919
for iter_inv in iter_inv_fns:
1920
request = [revid for revid in revision_ids if revid in missing]
1921
for inv, revid in iter_inv(request, ordering):
1924
missing.remove(inv.revision_id)
1925
if ordering != 'unordered':
1929
if order_as_requested:
1930
# Yield as many results as we can while preserving order.
1931
while next_revid in invs:
1932
inv = invs.pop(next_revid)
1933
yield inv, inv.revision_id
1935
next_revid = order.pop()
1937
# We still want to fully consume the stream, just
1938
# in case it is not actually finished at this point
1941
except errors.UnknownSmartMethod:
1942
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
1946
if order_as_requested:
1947
if next_revid is not None:
1948
yield None, next_revid
1951
yield invs.get(revid), revid
1954
yield None, missing.pop()
1957
def get_revision(self, revision_id):
1958
return self.get_revisions([revision_id])[0]
1960
def get_transaction(self):
1962
return self._real_repository.get_transaction()
1965
def clone(self, a_bzrdir, revision_id=None):
1966
dest_repo = self._create_sprouting_repo(
1967
a_bzrdir, shared=self.is_shared())
1968
self.copy_content_into(dest_repo, revision_id)
1971
def make_working_trees(self):
1972
"""See Repository.make_working_trees"""
1973
path = self.bzrdir._path_for_remote_call(self._client)
1975
response = self._call('Repository.make_working_trees', path)
1976
except errors.UnknownSmartMethod:
1978
return self._real_repository.make_working_trees()
1979
if response[0] not in ('yes', 'no'):
1980
raise SmartProtocolError('unexpected response code %s' % (response,))
1981
return response[0] == 'yes'
1983
def refresh_data(self):
1984
"""Re-read any data needed to synchronise with disk.
1986
This method is intended to be called after another repository instance
1987
(such as one used by a smart server) has inserted data into the
1988
repository. On all repositories this will work outside of write groups.
1989
Some repository formats (pack and newer for bzrlib native formats)
1990
support refresh_data inside write groups. If called inside a write
1991
group on a repository that does not support refreshing in a write group
1992
IsInWriteGroupError will be raised.
1994
if self._real_repository is not None:
1995
self._real_repository.refresh_data()
1997
def revision_ids_to_search_result(self, result_set):
1998
"""Convert a set of revision ids to a graph SearchResult."""
1999
result_parents = set()
2000
for parents in self.get_graph().get_parent_map(
2001
result_set).itervalues():
2002
result_parents.update(parents)
2003
included_keys = result_set.intersection(result_parents)
2004
start_keys = result_set.difference(included_keys)
2005
exclude_keys = result_parents.difference(result_set)
2006
result = vf_search.SearchResult(start_keys, exclude_keys,
2007
len(result_set), result_set)
2011
def search_missing_revision_ids(self, other,
2012
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2013
find_ghosts=True, revision_ids=None, if_present_ids=None,
2015
"""Return the revision ids that other has that this does not.
2017
These are returned in topological order.
2019
revision_id: only return revision ids included by revision_id.
2021
if symbol_versioning.deprecated_passed(revision_id):
2022
symbol_versioning.warn(
2023
'search_missing_revision_ids(revision_id=...) was '
2024
'deprecated in 2.4. Use revision_ids=[...] instead.',
2025
DeprecationWarning, stacklevel=2)
2026
if revision_ids is not None:
2027
raise AssertionError(
2028
'revision_ids is mutually exclusive with revision_id')
2029
if revision_id is not None:
2030
revision_ids = [revision_id]
2031
inter_repo = _mod_repository.InterRepository.get(other, self)
2032
return inter_repo.search_missing_revision_ids(
2033
find_ghosts=find_ghosts, revision_ids=revision_ids,
2034
if_present_ids=if_present_ids, limit=limit)
2036
def fetch(self, source, revision_id=None, find_ghosts=False,
2038
# No base implementation to use as RemoteRepository is not a subclass
2039
# of Repository; so this is a copy of Repository.fetch().
2040
if fetch_spec is not None and revision_id is not None:
2041
raise AssertionError(
2042
"fetch_spec and revision_id are mutually exclusive.")
2043
if self.is_in_write_group():
2044
raise errors.InternalBzrError(
2045
"May not fetch while in a write group.")
2046
# fast path same-url fetch operations
2047
if (self.has_same_location(source)
2048
and fetch_spec is None
2049
and self._has_same_fallbacks(source)):
2050
# check that last_revision is in 'from' and then return a
2052
if (revision_id is not None and
2053
not _mod_revision.is_null(revision_id)):
2054
self.get_revision(revision_id)
2056
# if there is no specific appropriate InterRepository, this will get
2057
# the InterRepository base class, which raises an
2058
# IncompatibleRepositories when asked to fetch.
2059
inter = _mod_repository.InterRepository.get(source, self)
2060
if (fetch_spec is not None and
2061
not getattr(inter, "supports_fetch_spec", False)):
2062
raise errors.UnsupportedOperation(
2063
"fetch_spec not supported for %r" % inter)
2064
return inter.fetch(revision_id=revision_id,
2065
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2067
def create_bundle(self, target, base, fileobj, format=None):
2069
self._real_repository.create_bundle(target, base, fileobj, format)
2072
@symbol_versioning.deprecated_method(
2073
symbol_versioning.deprecated_in((2, 4, 0)))
2074
def get_ancestry(self, revision_id, topo_sorted=True):
2076
return self._real_repository.get_ancestry(revision_id, topo_sorted)
2078
def fileids_altered_by_revision_ids(self, revision_ids):
2080
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2082
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2084
return self._real_repository._get_versioned_file_checker(
2085
revisions, revision_versions_cache)
2087
def _iter_files_bytes_rpc(self, desired_files, absent):
2088
path = self.bzrdir._path_for_remote_call(self._client)
2091
for (file_id, revid, identifier) in desired_files:
2092
lines.append("%s\0%s" % (
2093
osutils.safe_file_id(file_id),
2094
osutils.safe_revision_id(revid)))
2095
identifiers.append(identifier)
2096
(response_tuple, response_handler) = (
2097
self._call_with_body_bytes_expecting_body(
2098
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2099
if response_tuple != ('ok', ):
2100
response_handler.cancel_read_body()
2101
raise errors.UnexpectedSmartServerResponse(response_tuple)
2102
byte_stream = response_handler.read_streamed_body()
2103
def decompress_stream(start, byte_stream, unused):
2104
decompressor = zlib.decompressobj()
2105
yield decompressor.decompress(start)
2106
while decompressor.unused_data == "":
2108
data = byte_stream.next()
2109
except StopIteration:
2111
yield decompressor.decompress(data)
2112
yield decompressor.flush()
2113
unused.append(decompressor.unused_data)
2116
while not "\n" in unused:
2117
unused += byte_stream.next()
2118
header, rest = unused.split("\n", 1)
2119
args = header.split("\0")
2120
if args[0] == "absent":
2121
absent[identifiers[int(args[3])]] = (args[1], args[2])
2124
elif args[0] == "ok":
2127
raise errors.UnexpectedSmartServerResponse(args)
2129
yield (identifiers[idx],
2130
decompress_stream(rest, byte_stream, unused_chunks))
2131
unused = "".join(unused_chunks)
2133
def iter_files_bytes(self, desired_files):
2134
"""See Repository.iter_file_bytes.
2138
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2139
desired_files, absent):
2140
yield identifier, bytes_iterator
2141
for fallback in self._fallback_repositories:
2144
desired_files = [(key[0], key[1], identifier) for
2145
(identifier, key) in absent.iteritems()]
2146
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2147
del absent[identifier]
2148
yield identifier, bytes_iterator
2150
# There may be more missing items, but raise an exception
2152
missing_identifier = absent.keys()[0]
2153
missing_key = absent[missing_identifier]
2154
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2155
file_id=missing_key[0])
2156
except errors.UnknownSmartMethod:
2158
for (identifier, bytes_iterator) in (
2159
self._real_repository.iter_files_bytes(desired_files)):
2160
yield identifier, bytes_iterator
2162
def get_cached_parent_map(self, revision_ids):
2163
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2164
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2166
def get_parent_map(self, revision_ids):
2167
"""See bzrlib.Graph.get_parent_map()."""
2168
return self._make_parents_provider().get_parent_map(revision_ids)
2170
def _get_parent_map_rpc(self, keys):
2171
"""Helper for get_parent_map that performs the RPC."""
2172
medium = self._client._medium
2173
if medium._is_remote_before((1, 2)):
2174
# We already found out that the server can't understand
2175
# Repository.get_parent_map requests, so just fetch the whole
2178
# Note that this reads the whole graph, when only some keys are
2179
# wanted. On this old server there's no way (?) to get them all
2180
# in one go, and the user probably will have seen a warning about
2181
# the server being old anyhow.
2182
rg = self._get_revision_graph(None)
2183
# There is an API discrepancy between get_parent_map and
2184
# get_revision_graph. Specifically, a "key:()" pair in
2185
# get_revision_graph just means a node has no parents. For
2186
# "get_parent_map" it means the node is a ghost. So fix up the
2187
# graph to correct this.
2188
# https://bugs.launchpad.net/bzr/+bug/214894
2189
# There is one other "bug" which is that ghosts in
2190
# get_revision_graph() are not returned at all. But we won't worry
2191
# about that for now.
2192
for node_id, parent_ids in rg.iteritems():
2193
if parent_ids == ():
2194
rg[node_id] = (NULL_REVISION,)
2195
rg[NULL_REVISION] = ()
2200
raise ValueError('get_parent_map(None) is not valid')
2201
if NULL_REVISION in keys:
2202
keys.discard(NULL_REVISION)
2203
found_parents = {NULL_REVISION:()}
2205
return found_parents
2208
# TODO(Needs analysis): We could assume that the keys being requested
2209
# from get_parent_map are in a breadth first search, so typically they
2210
# will all be depth N from some common parent, and we don't have to
2211
# have the server iterate from the root parent, but rather from the
2212
# keys we're searching; and just tell the server the keyspace we
2213
# already have; but this may be more traffic again.
2215
# Transform self._parents_map into a search request recipe.
2216
# TODO: Manage this incrementally to avoid covering the same path
2217
# repeatedly. (The server will have to on each request, but the less
2218
# work done the better).
2220
# Negative caching notes:
2221
# new server sends missing when a request including the revid
2222
# 'include-missing:' is present in the request.
2223
# missing keys are serialised as missing:X, and we then call
2224
# provider.note_missing(X) for-all X
2225
parents_map = self._unstacked_provider.get_cached_map()
2226
if parents_map is None:
2227
# Repository is not locked, so there's no cache.
2229
if _DEFAULT_SEARCH_DEPTH <= 0:
2230
(start_set, stop_keys,
2231
key_count) = vf_search.search_result_from_parent_map(
2232
parents_map, self._unstacked_provider.missing_keys)
2234
(start_set, stop_keys,
2235
key_count) = vf_search.limited_search_result_from_parent_map(
2236
parents_map, self._unstacked_provider.missing_keys,
2237
keys, depth=_DEFAULT_SEARCH_DEPTH)
2238
recipe = ('manual', start_set, stop_keys, key_count)
2239
body = self._serialise_search_recipe(recipe)
2240
path = self.bzrdir._path_for_remote_call(self._client)
2242
if type(key) is not str:
2244
"key %r not a plain string" % (key,))
2245
verb = 'Repository.get_parent_map'
2246
args = (path, 'include-missing:') + tuple(keys)
2248
response = self._call_with_body_bytes_expecting_body(
2250
except errors.UnknownSmartMethod:
2251
# Server does not support this method, so get the whole graph.
2252
# Worse, we have to force a disconnection, because the server now
2253
# doesn't realise it has a body on the wire to consume, so the
2254
# only way to recover is to abandon the connection.
2256
'Server is too old for fast get_parent_map, reconnecting. '
2257
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2259
# To avoid having to disconnect repeatedly, we keep track of the
2260
# fact the server doesn't understand remote methods added in 1.2.
2261
medium._remember_remote_is_before((1, 2))
2262
# Recurse just once and we should use the fallback code.
2263
return self._get_parent_map_rpc(keys)
2264
response_tuple, response_handler = response
2265
if response_tuple[0] not in ['ok']:
2266
response_handler.cancel_read_body()
2267
raise errors.UnexpectedSmartServerResponse(response_tuple)
2268
if response_tuple[0] == 'ok':
2269
coded = bz2.decompress(response_handler.read_body_bytes())
2271
# no revisions found
2273
lines = coded.split('\n')
2276
d = tuple(line.split())
2278
revision_graph[d[0]] = d[1:]
2281
if d[0].startswith('missing:'):
2283
self._unstacked_provider.note_missing_key(revid)
2285
# no parents - so give the Graph result
2287
revision_graph[d[0]] = (NULL_REVISION,)
2288
return revision_graph
2291
def get_signature_text(self, revision_id):
2292
path = self.bzrdir._path_for_remote_call(self._client)
2294
response_tuple, response_handler = self._call_expecting_body(
2295
'Repository.get_revision_signature_text', path, revision_id)
2296
except errors.UnknownSmartMethod:
2298
return self._real_repository.get_signature_text(revision_id)
2299
except errors.NoSuchRevision, err:
2300
for fallback in self._fallback_repositories:
2302
return fallback.get_signature_text(revision_id)
2303
except errors.NoSuchRevision:
2307
if response_tuple[0] != 'ok':
2308
raise errors.UnexpectedSmartServerResponse(response_tuple)
2309
return response_handler.read_body_bytes()
2312
def _get_inventory_xml(self, revision_id):
2313
# This call is used by older working tree formats,
2314
# which stored a serialized basis inventory.
2316
return self._real_repository._get_inventory_xml(revision_id)
2319
def reconcile(self, other=None, thorough=False):
2320
from bzrlib.reconcile import RepoReconciler
2321
path = self.bzrdir._path_for_remote_call(self._client)
2323
response, handler = self._call_expecting_body(
2324
'Repository.reconcile', path, self._lock_token)
2325
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2327
return self._real_repository.reconcile(other=other, thorough=thorough)
2328
if response != ('ok', ):
2329
raise errors.UnexpectedSmartServerResponse(response)
2330
body = handler.read_body_bytes()
2331
result = RepoReconciler(self)
2332
for line in body.split('\n'):
2335
key, val_text = line.split(':')
2336
if key == "garbage_inventories":
2337
result.garbage_inventories = int(val_text)
2338
elif key == "inconsistent_parents":
2339
result.inconsistent_parents = int(val_text)
2341
mutter("unknown reconcile key %r" % key)
2344
def all_revision_ids(self):
2345
path = self.bzrdir._path_for_remote_call(self._client)
2347
response_tuple, response_handler = self._call_expecting_body(
2348
"Repository.all_revision_ids", path)
2349
except errors.UnknownSmartMethod:
2351
return self._real_repository.all_revision_ids()
2352
if response_tuple != ("ok", ):
2353
raise errors.UnexpectedSmartServerResponse(response_tuple)
2354
revids = set(response_handler.read_body_bytes().splitlines())
2355
for fallback in self._fallback_repositories:
2356
revids.update(set(fallback.all_revision_ids()))
2359
def _filtered_revision_trees(self, revision_ids, file_ids):
2360
"""Return Tree for a revision on this branch with only some files.
2362
:param revision_ids: a sequence of revision-ids;
2363
a revision-id may not be None or 'null:'
2364
:param file_ids: if not None, the result is filtered
2365
so that only those file-ids, their parents and their
2366
children are included.
2368
inventories = self.iter_inventories(revision_ids)
2369
for inv in inventories:
2370
# Should we introduce a FilteredRevisionTree class rather
2371
# than pre-filter the inventory here?
2372
filtered_inv = inv.filter(file_ids)
2373
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2376
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2377
medium = self._client._medium
2378
if medium._is_remote_before((1, 2)):
2380
for delta in self._real_repository.get_deltas_for_revisions(
2381
revisions, specific_fileids):
2384
# Get the revision-ids of interest
2385
required_trees = set()
2386
for revision in revisions:
2387
required_trees.add(revision.revision_id)
2388
required_trees.update(revision.parent_ids[:1])
2390
# Get the matching filtered trees. Note that it's more
2391
# efficient to pass filtered trees to changes_from() rather
2392
# than doing the filtering afterwards. changes_from() could
2393
# arguably do the filtering itself but it's path-based, not
2394
# file-id based, so filtering before or afterwards is
2396
if specific_fileids is None:
2397
trees = dict((t.get_revision_id(), t) for
2398
t in self.revision_trees(required_trees))
2400
trees = dict((t.get_revision_id(), t) for
2401
t in self._filtered_revision_trees(required_trees,
2404
# Calculate the deltas
2405
for revision in revisions:
2406
if not revision.parent_ids:
2407
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2409
old_tree = trees[revision.parent_ids[0]]
2410
yield trees[revision.revision_id].changes_from(old_tree)
2413
def get_revision_delta(self, revision_id, specific_fileids=None):
2414
r = self.get_revision(revision_id)
2415
return list(self.get_deltas_for_revisions([r],
2416
specific_fileids=specific_fileids))[0]
2419
def revision_trees(self, revision_ids):
2420
inventories = self.iter_inventories(revision_ids)
2421
for inv in inventories:
2422
yield InventoryRevisionTree(self, inv, inv.revision_id)
2425
def get_revision_reconcile(self, revision_id):
2427
return self._real_repository.get_revision_reconcile(revision_id)
2430
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2432
return self._real_repository.check(revision_ids=revision_ids,
2433
callback_refs=callback_refs, check_repo=check_repo)
2435
def copy_content_into(self, destination, revision_id=None):
2436
"""Make a complete copy of the content in self into destination.
2438
This is a destructive operation! Do not use it on existing
2441
interrepo = _mod_repository.InterRepository.get(self, destination)
2442
return interrepo.copy_content(revision_id)
2444
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2445
# get a tarball of the remote repository, and copy from that into the
2448
# TODO: Maybe a progress bar while streaming the tarball?
2449
note(gettext("Copying repository content as tarball..."))
2450
tar_file = self._get_tarball('bz2')
2451
if tar_file is None:
2453
destination = to_bzrdir.create_repository()
2455
tar = tarfile.open('repository', fileobj=tar_file,
2457
tmpdir = osutils.mkdtemp()
2459
_extract_tar(tar, tmpdir)
2460
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2461
tmp_repo = tmp_bzrdir.open_repository()
2462
tmp_repo.copy_content_into(destination, revision_id)
2464
osutils.rmtree(tmpdir)
2468
# TODO: Suggestion from john: using external tar is much faster than
2469
# python's tarfile library, but it may not work on windows.
2472
def inventories(self):
2473
"""Decorate the real repository for now.
2475
In the long term a full blown network facility is needed to
2476
avoid creating a real repository object locally.
2479
return self._real_repository.inventories
2482
def pack(self, hint=None, clean_obsolete_packs=False):
2483
"""Compress the data within the repository.
2488
body = "".join([l+"\n" for l in hint])
2489
path = self.bzrdir._path_for_remote_call(self._client)
2491
response, handler = self._call_with_body_bytes_expecting_body(
2492
'Repository.pack', (path, self._lock_token,
2493
str(clean_obsolete_packs)), body)
2494
except errors.UnknownSmartMethod:
2496
return self._real_repository.pack(hint=hint,
2497
clean_obsolete_packs=clean_obsolete_packs)
2498
handler.cancel_read_body()
2499
if response != ('ok', ):
2500
raise errors.UnexpectedSmartServerResponse(response)
2503
def revisions(self):
2504
"""Decorate the real repository for now.
2506
In the long term a full blown network facility is needed.
2509
return self._real_repository.revisions
2511
def set_make_working_trees(self, new_value):
2513
new_value_str = "True"
2515
new_value_str = "False"
2516
path = self.bzrdir._path_for_remote_call(self._client)
2518
response = self._call(
2519
'Repository.set_make_working_trees', path, new_value_str)
2520
except errors.UnknownSmartMethod:
2522
self._real_repository.set_make_working_trees(new_value)
2524
if response[0] != 'ok':
2525
raise errors.UnexpectedSmartServerResponse(response)
2528
def signatures(self):
2529
"""Decorate the real repository for now.
2531
In the long term a full blown network facility is needed to avoid
2532
creating a real repository object locally.
2535
return self._real_repository.signatures
2538
def sign_revision(self, revision_id, gpg_strategy):
2539
testament = _mod_testament.Testament.from_revision(self, revision_id)
2540
plaintext = testament.as_short_text()
2541
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2545
"""Decorate the real repository for now.
2547
In the long term a full blown network facility is needed to avoid
2548
creating a real repository object locally.
2551
return self._real_repository.texts
2553
def _iter_revisions_rpc(self, revision_ids):
2554
body = "\n".join(revision_ids)
2555
path = self.bzrdir._path_for_remote_call(self._client)
2556
response_tuple, response_handler = (
2557
self._call_with_body_bytes_expecting_body(
2558
"Repository.iter_revisions", (path, ), body))
2559
if response_tuple[0] != "ok":
2560
raise errors.UnexpectedSmartServerResponse(response_tuple)
2561
serializer_format = response_tuple[1]
2562
serializer = serializer_format_registry.get(serializer_format)
2563
byte_stream = response_handler.read_streamed_body()
2564
decompressor = zlib.decompressobj()
2566
for bytes in byte_stream:
2567
chunks.append(decompressor.decompress(bytes))
2568
if decompressor.unused_data != "":
2569
chunks.append(decompressor.flush())
2570
yield serializer.read_revision_from_string("".join(chunks))
2571
unused = decompressor.unused_data
2572
decompressor = zlib.decompressobj()
2573
chunks = [decompressor.decompress(unused)]
2574
chunks.append(decompressor.flush())
2575
text = "".join(chunks)
2577
yield serializer.read_revision_from_string("".join(chunks))
2580
def get_revisions(self, revision_ids):
2581
if revision_ids is None:
2582
revision_ids = self.all_revision_ids()
2584
for rev_id in revision_ids:
2585
if not rev_id or not isinstance(rev_id, basestring):
2586
raise errors.InvalidRevisionId(
2587
revision_id=rev_id, branch=self)
2589
missing = set(revision_ids)
2591
for rev in self._iter_revisions_rpc(revision_ids):
2592
missing.remove(rev.revision_id)
2593
revs[rev.revision_id] = rev
2594
except errors.UnknownSmartMethod:
2596
return self._real_repository.get_revisions(revision_ids)
2597
for fallback in self._fallback_repositories:
2600
for revid in list(missing):
2601
# XXX JRV 2011-11-20: It would be nice if there was a
2602
# public method on Repository that could be used to query
2603
# for revision objects *without* failing completely if one
2604
# was missing. There is VersionedFileRepository._iter_revisions,
2605
# but unfortunately that's private and not provided by
2606
# all repository implementations.
2608
revs[revid] = fallback.get_revision(revid)
2609
except errors.NoSuchRevision:
2612
missing.remove(revid)
2614
raise errors.NoSuchRevision(self, list(missing)[0])
2615
return [revs[revid] for revid in revision_ids]
2617
def supports_rich_root(self):
2618
return self._format.rich_root_data
2620
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2621
def iter_reverse_revision_history(self, revision_id):
2623
return self._real_repository.iter_reverse_revision_history(revision_id)
2626
def _serializer(self):
2627
return self._format._serializer
2630
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2631
signature = gpg_strategy.sign(plaintext)
2632
self.add_signature_text(revision_id, signature)
2634
def add_signature_text(self, revision_id, signature):
2635
if self._real_repository:
2636
# If there is a real repository the write group will
2637
# be in the real repository as well, so use that:
2639
return self._real_repository.add_signature_text(
2640
revision_id, signature)
2641
path = self.bzrdir._path_for_remote_call(self._client)
2642
response, handler = self._call_with_body_bytes_expecting_body(
2643
'Repository.add_signature_text', (path, self._lock_token,
2644
revision_id) + tuple(self._write_group_tokens), signature)
2645
handler.cancel_read_body()
2647
if response[0] != 'ok':
2648
raise errors.UnexpectedSmartServerResponse(response)
2649
self._write_group_tokens = response[1:]
2651
def has_signature_for_revision_id(self, revision_id):
2652
path = self.bzrdir._path_for_remote_call(self._client)
2654
response = self._call('Repository.has_signature_for_revision_id',
2656
except errors.UnknownSmartMethod:
2658
return self._real_repository.has_signature_for_revision_id(
2660
if response[0] not in ('yes', 'no'):
2661
raise SmartProtocolError('unexpected response code %s' % (response,))
2662
if response[0] == 'yes':
2664
for fallback in self._fallback_repositories:
2665
if fallback.has_signature_for_revision_id(revision_id):
2670
def verify_revision_signature(self, revision_id, gpg_strategy):
2671
if not self.has_signature_for_revision_id(revision_id):
2672
return gpg.SIGNATURE_NOT_SIGNED, None
2673
signature = self.get_signature_text(revision_id)
2675
testament = _mod_testament.Testament.from_revision(self, revision_id)
2676
plaintext = testament.as_short_text()
2678
return gpg_strategy.verify(signature, plaintext)
2680
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2682
return self._real_repository.item_keys_introduced_by(revision_ids,
2683
_files_pb=_files_pb)
2685
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2687
return self._real_repository._find_inconsistent_revision_parents(
2690
def _check_for_inconsistent_revision_parents(self):
2692
return self._real_repository._check_for_inconsistent_revision_parents()
2694
def _make_parents_provider(self, other=None):
2695
providers = [self._unstacked_provider]
2696
if other is not None:
2697
providers.insert(0, other)
2698
return graph.StackedParentsProvider(_LazyListJoin(
2699
providers, self._fallback_repositories))
2701
def _serialise_search_recipe(self, recipe):
2702
"""Serialise a graph search recipe.
2704
:param recipe: A search recipe (start, stop, count).
2705
:return: Serialised bytes.
2707
start_keys = ' '.join(recipe[1])
2708
stop_keys = ' '.join(recipe[2])
2709
count = str(recipe[3])
2710
return '\n'.join((start_keys, stop_keys, count))
2712
def _serialise_search_result(self, search_result):
2713
parts = search_result.get_network_struct()
2714
return '\n'.join(parts)
2717
path = self.bzrdir._path_for_remote_call(self._client)
2719
response = self._call('PackRepository.autopack', path)
2720
except errors.UnknownSmartMethod:
2722
self._real_repository._pack_collection.autopack()
2725
if response[0] != 'ok':
2726
raise errors.UnexpectedSmartServerResponse(response)
2729
class RemoteStreamSink(vf_repository.StreamSink):
2731
def _insert_real(self, stream, src_format, resume_tokens):
2732
self.target_repo._ensure_real()
2733
sink = self.target_repo._real_repository._get_sink()
2734
result = sink.insert_stream(stream, src_format, resume_tokens)
2736
self.target_repo.autopack()
2739
def insert_stream(self, stream, src_format, resume_tokens):
2740
target = self.target_repo
2741
target._unstacked_provider.missing_keys.clear()
2742
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2743
if target._lock_token:
2744
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2745
lock_args = (target._lock_token or '',)
2747
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2749
client = target._client
2750
medium = client._medium
2751
path = target.bzrdir._path_for_remote_call(client)
2752
# Probe for the verb to use with an empty stream before sending the
2753
# real stream to it. We do this both to avoid the risk of sending a
2754
# large request that is then rejected, and because we don't want to
2755
# implement a way to buffer, rewind, or restart the stream.
2757
for verb, required_version in candidate_calls:
2758
if medium._is_remote_before(required_version):
2761
# We've already done the probing (and set _is_remote_before) on
2762
# a previous insert.
2765
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2767
response = client.call_with_body_stream(
2768
(verb, path, '') + lock_args, byte_stream)
2769
except errors.UnknownSmartMethod:
2770
medium._remember_remote_is_before(required_version)
2776
return self._insert_real(stream, src_format, resume_tokens)
2777
self._last_inv_record = None
2778
self._last_substream = None
2779
if required_version < (1, 19):
2780
# Remote side doesn't support inventory deltas. Wrap the stream to
2781
# make sure we don't send any. If the stream contains inventory
2782
# deltas we'll interrupt the smart insert_stream request and
2784
stream = self._stop_stream_if_inventory_delta(stream)
2785
byte_stream = smart_repo._stream_to_byte_stream(
2787
resume_tokens = ' '.join(resume_tokens)
2788
response = client.call_with_body_stream(
2789
(verb, path, resume_tokens) + lock_args, byte_stream)
2790
if response[0][0] not in ('ok', 'missing-basis'):
2791
raise errors.UnexpectedSmartServerResponse(response)
2792
if self._last_substream is not None:
2793
# The stream included an inventory-delta record, but the remote
2794
# side isn't new enough to support them. So we need to send the
2795
# rest of the stream via VFS.
2796
self.target_repo.refresh_data()
2797
return self._resume_stream_with_vfs(response, src_format)
2798
if response[0][0] == 'missing-basis':
2799
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2800
resume_tokens = tokens
2801
return resume_tokens, set(missing_keys)
2803
self.target_repo.refresh_data()
2806
def _resume_stream_with_vfs(self, response, src_format):
2807
"""Resume sending a stream via VFS, first resending the record and
2808
substream that couldn't be sent via an insert_stream verb.
2810
if response[0][0] == 'missing-basis':
2811
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2812
# Ignore missing_keys, we haven't finished inserting yet
2815
def resume_substream():
2816
# Yield the substream that was interrupted.
2817
for record in self._last_substream:
2819
self._last_substream = None
2820
def resume_stream():
2821
# Finish sending the interrupted substream
2822
yield ('inventory-deltas', resume_substream())
2823
# Then simply continue sending the rest of the stream.
2824
for substream_kind, substream in self._last_stream:
2825
yield substream_kind, substream
2826
return self._insert_real(resume_stream(), src_format, tokens)
2828
def _stop_stream_if_inventory_delta(self, stream):
2829
"""Normally this just lets the original stream pass-through unchanged.
2831
However if any 'inventory-deltas' substream occurs it will stop
2832
streaming, and store the interrupted substream and stream in
2833
self._last_substream and self._last_stream so that the stream can be
2834
resumed by _resume_stream_with_vfs.
2837
stream_iter = iter(stream)
2838
for substream_kind, substream in stream_iter:
2839
if substream_kind == 'inventory-deltas':
2840
self._last_substream = substream
2841
self._last_stream = stream_iter
2844
yield substream_kind, substream
2847
class RemoteStreamSource(vf_repository.StreamSource):
2848
"""Stream data from a remote server."""
2850
def get_stream(self, search):
2851
if (self.from_repository._fallback_repositories and
2852
self.to_format._fetch_order == 'topological'):
2853
return self._real_stream(self.from_repository, search)
2856
repos = [self.from_repository]
2862
repos.extend(repo._fallback_repositories)
2863
sources.append(repo)
2864
return self.missing_parents_chain(search, sources)
2866
def get_stream_for_missing_keys(self, missing_keys):
2867
self.from_repository._ensure_real()
2868
real_repo = self.from_repository._real_repository
2869
real_source = real_repo._get_source(self.to_format)
2870
return real_source.get_stream_for_missing_keys(missing_keys)
2872
def _real_stream(self, repo, search):
2873
"""Get a stream for search from repo.
2875
This never called RemoteStreamSource.get_stream, and is a helper
2876
for RemoteStreamSource._get_stream to allow getting a stream
2877
reliably whether fallback back because of old servers or trying
2878
to stream from a non-RemoteRepository (which the stacked support
2881
source = repo._get_source(self.to_format)
2882
if isinstance(source, RemoteStreamSource):
2884
source = repo._real_repository._get_source(self.to_format)
2885
return source.get_stream(search)
2887
def _get_stream(self, repo, search):
2888
"""Core worker to get a stream from repo for search.
2890
This is used by both get_stream and the stacking support logic. It
2891
deliberately gets a stream for repo which does not need to be
2892
self.from_repository. In the event that repo is not Remote, or
2893
cannot do a smart stream, a fallback is made to the generic
2894
repository._get_stream() interface, via self._real_stream.
2896
In the event of stacking, streams from _get_stream will not
2897
contain all the data for search - this is normal (see get_stream).
2899
:param repo: A repository.
2900
:param search: A search.
2902
# Fallbacks may be non-smart
2903
if not isinstance(repo, RemoteRepository):
2904
return self._real_stream(repo, search)
2905
client = repo._client
2906
medium = client._medium
2907
path = repo.bzrdir._path_for_remote_call(client)
2908
search_bytes = repo._serialise_search_result(search)
2909
args = (path, self.to_format.network_name())
2911
('Repository.get_stream_1.19', (1, 19)),
2912
('Repository.get_stream', (1, 13))]
2915
for verb, version in candidate_verbs:
2916
if medium._is_remote_before(version):
2919
response = repo._call_with_body_bytes_expecting_body(
2920
verb, args, search_bytes)
2921
except errors.UnknownSmartMethod:
2922
medium._remember_remote_is_before(version)
2923
except errors.UnknownErrorFromSmartServer, e:
2924
if isinstance(search, vf_search.EverythingResult):
2925
error_verb = e.error_from_smart_server.error_verb
2926
if error_verb == 'BadSearch':
2927
# Pre-2.4 servers don't support this sort of search.
2928
# XXX: perhaps falling back to VFS on BadSearch is a
2929
# good idea in general? It might provide a little bit
2930
# of protection against client-side bugs.
2931
medium._remember_remote_is_before((2, 4))
2935
response_tuple, response_handler = response
2939
return self._real_stream(repo, search)
2940
if response_tuple[0] != 'ok':
2941
raise errors.UnexpectedSmartServerResponse(response_tuple)
2942
byte_stream = response_handler.read_streamed_body()
2943
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2944
self._record_counter)
2945
if src_format.network_name() != repo._format.network_name():
2946
raise AssertionError(
2947
"Mismatched RemoteRepository and stream src %r, %r" % (
2948
src_format.network_name(), repo._format.network_name()))
2951
def missing_parents_chain(self, search, sources):
2952
"""Chain multiple streams together to handle stacking.
2954
:param search: The overall search to satisfy with streams.
2955
:param sources: A list of Repository objects to query.
2957
self.from_serialiser = self.from_repository._format._serializer
2958
self.seen_revs = set()
2959
self.referenced_revs = set()
2960
# If there are heads in the search, or the key count is > 0, we are not
2962
while not search.is_empty() and len(sources) > 1:
2963
source = sources.pop(0)
2964
stream = self._get_stream(source, search)
2965
for kind, substream in stream:
2966
if kind != 'revisions':
2967
yield kind, substream
2969
yield kind, self.missing_parents_rev_handler(substream)
2970
search = search.refine(self.seen_revs, self.referenced_revs)
2971
self.seen_revs = set()
2972
self.referenced_revs = set()
2973
if not search.is_empty():
2974
for kind, stream in self._get_stream(sources[0], search):
2977
def missing_parents_rev_handler(self, substream):
2978
for content in substream:
2979
revision_bytes = content.get_bytes_as('fulltext')
2980
revision = self.from_serialiser.read_revision_from_string(
2982
self.seen_revs.add(content.key[-1])
2983
self.referenced_revs.update(revision.parent_ids)
2987
class RemoteBranchLockableFiles(LockableFiles):
2988
"""A 'LockableFiles' implementation that talks to a smart server.
2990
This is not a public interface class.
2993
def __init__(self, bzrdir, _client):
2994
self.bzrdir = bzrdir
2995
self._client = _client
2996
self._need_find_modes = True
2997
LockableFiles.__init__(
2998
self, bzrdir.get_branch_transport(None),
2999
'lock', lockdir.LockDir)
3001
def _find_modes(self):
3002
# RemoteBranches don't let the client set the mode of control files.
3003
self._dir_mode = None
3004
self._file_mode = None
3007
class RemoteBranchFormat(branch.BranchFormat):
3009
def __init__(self, network_name=None):
3010
super(RemoteBranchFormat, self).__init__()
3011
self._matchingbzrdir = RemoteBzrDirFormat()
3012
self._matchingbzrdir.set_branch_format(self)
3013
self._custom_format = None
3014
self._network_name = network_name
3016
def __eq__(self, other):
3017
return (isinstance(other, RemoteBranchFormat) and
3018
self.__dict__ == other.__dict__)
3020
def _ensure_real(self):
3021
if self._custom_format is None:
3023
self._custom_format = branch.network_format_registry.get(
3026
raise errors.UnknownFormatError(kind='branch',
3027
format=self._network_name)
3029
def get_format_description(self):
3031
return 'Remote: ' + self._custom_format.get_format_description()
3033
def network_name(self):
3034
return self._network_name
3036
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3037
return a_bzrdir.open_branch(name=name,
3038
ignore_fallbacks=ignore_fallbacks)
3040
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3041
# Initialisation when using a local bzrdir object, or a non-vfs init
3042
# method is not available on the server.
3043
# self._custom_format is always set - the start of initialize ensures
3045
if isinstance(a_bzrdir, RemoteBzrDir):
3046
a_bzrdir._ensure_real()
3047
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3048
name, append_revisions_only=append_revisions_only)
3050
# We assume the bzrdir is parameterised; it may not be.
3051
result = self._custom_format.initialize(a_bzrdir, name,
3052
append_revisions_only=append_revisions_only)
3053
if (isinstance(a_bzrdir, RemoteBzrDir) and
3054
not isinstance(result, RemoteBranch)):
3055
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3059
def initialize(self, a_bzrdir, name=None, repository=None,
3060
append_revisions_only=None):
3061
# 1) get the network name to use.
3062
if self._custom_format:
3063
network_name = self._custom_format.network_name()
3065
# Select the current bzrlib default and ask for that.
3066
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3067
reference_format = reference_bzrdir_format.get_branch_format()
3068
self._custom_format = reference_format
3069
network_name = reference_format.network_name()
3070
# Being asked to create on a non RemoteBzrDir:
3071
if not isinstance(a_bzrdir, RemoteBzrDir):
3072
return self._vfs_initialize(a_bzrdir, name=name,
3073
append_revisions_only=append_revisions_only)
3074
medium = a_bzrdir._client._medium
3075
if medium._is_remote_before((1, 13)):
3076
return self._vfs_initialize(a_bzrdir, name=name,
3077
append_revisions_only=append_revisions_only)
3078
# Creating on a remote bzr dir.
3079
# 2) try direct creation via RPC
3080
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3081
if name is not None:
3082
# XXX JRV20100304: Support creating colocated branches
3083
raise errors.NoColocatedBranchSupport(self)
3084
verb = 'BzrDir.create_branch'
3086
response = a_bzrdir._call(verb, path, network_name)
3087
except errors.UnknownSmartMethod:
3088
# Fallback - use vfs methods
3089
medium._remember_remote_is_before((1, 13))
3090
return self._vfs_initialize(a_bzrdir, name=name,
3091
append_revisions_only=append_revisions_only)
3092
if response[0] != 'ok':
3093
raise errors.UnexpectedSmartServerResponse(response)
3094
# Turn the response into a RemoteRepository object.
3095
format = RemoteBranchFormat(network_name=response[1])
3096
repo_format = response_tuple_to_repo_format(response[3:])
3097
repo_path = response[2]
3098
if repository is not None:
3099
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3100
url_diff = urlutils.relative_url(repository.user_url,
3103
raise AssertionError(
3104
'repository.user_url %r does not match URL from server '
3105
'response (%r + %r)'
3106
% (repository.user_url, a_bzrdir.user_url, repo_path))
3107
remote_repo = repository
3110
repo_bzrdir = a_bzrdir
3112
repo_bzrdir = RemoteBzrDir(
3113
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3115
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3116
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3117
format=format, setup_stacking=False, name=name)
3118
if append_revisions_only:
3119
remote_branch.set_append_revisions_only(append_revisions_only)
3120
# XXX: We know this is a new branch, so it must have revno 0, revid
3121
# NULL_REVISION. Creating the branch locked would make this be unable
3122
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3123
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3124
return remote_branch
3126
def make_tags(self, branch):
3128
return self._custom_format.make_tags(branch)
3130
def supports_tags(self):
3131
# Remote branches might support tags, but we won't know until we
3132
# access the real remote branch.
3134
return self._custom_format.supports_tags()
3136
def supports_stacking(self):
3138
return self._custom_format.supports_stacking()
3140
def supports_set_append_revisions_only(self):
3142
return self._custom_format.supports_set_append_revisions_only()
3144
def _use_default_local_heads_to_fetch(self):
3145
# If the branch format is a metadir format *and* its heads_to_fetch
3146
# implementation is not overridden vs the base class, we can use the
3147
# base class logic rather than use the heads_to_fetch RPC. This is
3148
# usually cheaper in terms of net round trips, as the last-revision and
3149
# tags info fetched is cached and would be fetched anyway.
3151
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3152
branch_class = self._custom_format._branch_class()
3153
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3154
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3159
class RemoteBranchStore(config.IniFileStore):
3160
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3162
Note that this is specific to bzr-based formats.
3165
def __init__(self, branch):
3166
super(RemoteBranchStore, self).__init__()
3167
self.branch = branch
3169
self._real_store = None
3171
def lock_write(self, token=None):
3172
return self.branch.lock_write(token)
3175
return self.branch.unlock()
3179
# We need to be able to override the undecorated implementation
3180
self.save_without_locking()
3182
def save_without_locking(self):
3183
super(RemoteBranchStore, self).save()
3185
def external_url(self):
3186
return self.branch.user_url
3188
def _load_content(self):
3189
path = self.branch._remote_path()
3191
response, handler = self.branch._call_expecting_body(
3192
'Branch.get_config_file', path)
3193
except errors.UnknownSmartMethod:
3195
return self._real_store._load_content()
3196
if len(response) and response[0] != 'ok':
3197
raise errors.UnexpectedSmartServerResponse(response)
3198
return handler.read_body_bytes()
3200
def _save_content(self, content):
3201
path = self.branch._remote_path()
3203
response, handler = self.branch._call_with_body_bytes_expecting_body(
3204
'Branch.put_config_file', (path,
3205
self.branch._lock_token, self.branch._repo_lock_token),
3207
except errors.UnknownSmartMethod:
3209
return self._real_store._save_content(content)
3210
handler.cancel_read_body()
3211
if response != ('ok', ):
3212
raise errors.UnexpectedSmartServerResponse(response)
3214
def _ensure_real(self):
3215
self.branch._ensure_real()
3216
if self._real_store is None:
3217
self._real_store = config.BranchStore(self.branch)
3220
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3221
"""Branch stored on a server accessed by HPSS RPC.
3223
At the moment most operations are mapped down to simple file operations.
3226
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3227
_client=None, format=None, setup_stacking=True, name=None,
3228
possible_transports=None):
3229
"""Create a RemoteBranch instance.
3231
:param real_branch: An optional local implementation of the branch
3232
format, usually accessing the data via the VFS.
3233
:param _client: Private parameter for testing.
3234
:param format: A RemoteBranchFormat object, None to create one
3235
automatically. If supplied it should have a network_name already
3237
:param setup_stacking: If True make an RPC call to determine the
3238
stacked (or not) status of the branch. If False assume the branch
3240
:param name: Colocated branch name
3242
# We intentionally don't call the parent class's __init__, because it
3243
# will try to assign to self.tags, which is a property in this subclass.
3244
# And the parent's __init__ doesn't do much anyway.
3245
self.bzrdir = remote_bzrdir
3246
if _client is not None:
3247
self._client = _client
3249
self._client = remote_bzrdir._client
3250
self.repository = remote_repository
3251
if real_branch is not None:
3252
self._real_branch = real_branch
3253
# Give the remote repository the matching real repo.
3254
real_repo = self._real_branch.repository
3255
if isinstance(real_repo, RemoteRepository):
3256
real_repo._ensure_real()
3257
real_repo = real_repo._real_repository
3258
self.repository._set_real_repository(real_repo)
3259
# Give the branch the remote repository to let fast-pathing happen.
3260
self._real_branch.repository = self.repository
3262
self._real_branch = None
3263
# Fill out expected attributes of branch for bzrlib API users.
3264
self._clear_cached_state()
3265
# TODO: deprecate self.base in favor of user_url
3266
self.base = self.bzrdir.user_url
3268
self._control_files = None
3269
self._lock_mode = None
3270
self._lock_token = None
3271
self._repo_lock_token = None
3272
self._lock_count = 0
3273
self._leave_lock = False
3274
# Setup a format: note that we cannot call _ensure_real until all the
3275
# attributes above are set: This code cannot be moved higher up in this
3278
self._format = RemoteBranchFormat()
3279
if real_branch is not None:
3280
self._format._network_name = \
3281
self._real_branch._format.network_name()
3283
self._format = format
3284
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3285
# branch.open_branch method.
3286
self._real_ignore_fallbacks = not setup_stacking
3287
if not self._format._network_name:
3288
# Did not get from open_branchV2 - old server.
3290
self._format._network_name = \
3291
self._real_branch._format.network_name()
3292
self.tags = self._format.make_tags(self)
3293
# The base class init is not called, so we duplicate this:
3294
hooks = branch.Branch.hooks['open']
3297
self._is_stacked = False
3299
self._setup_stacking(possible_transports)
3301
def _setup_stacking(self, possible_transports):
3302
# configure stacking into the remote repository, by reading it from
3305
fallback_url = self.get_stacked_on_url()
3306
except (errors.NotStacked, errors.UnstackableBranchFormat,
3307
errors.UnstackableRepositoryFormat), e:
3309
self._is_stacked = True
3310
if possible_transports is None:
3311
possible_transports = []
3313
possible_transports = list(possible_transports)
3314
possible_transports.append(self.bzrdir.root_transport)
3315
self._activate_fallback_location(fallback_url,
3316
possible_transports=possible_transports)
3318
def _get_config(self):
3319
return RemoteBranchConfig(self)
3321
def _get_config_store(self):
3322
return RemoteBranchStore(self)
3324
def _get_real_transport(self):
3325
# if we try vfs access, return the real branch's vfs transport
3327
return self._real_branch._transport
3329
_transport = property(_get_real_transport)
3332
return "%s(%s)" % (self.__class__.__name__, self.base)
3336
def _ensure_real(self):
3337
"""Ensure that there is a _real_branch set.
3339
Used before calls to self._real_branch.
3341
if self._real_branch is None:
3342
if not vfs.vfs_enabled():
3343
raise AssertionError('smart server vfs must be enabled '
3344
'to use vfs implementation')
3345
self.bzrdir._ensure_real()
3346
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3347
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3348
if self.repository._real_repository is None:
3349
# Give the remote repository the matching real repo.
3350
real_repo = self._real_branch.repository
3351
if isinstance(real_repo, RemoteRepository):
3352
real_repo._ensure_real()
3353
real_repo = real_repo._real_repository
3354
self.repository._set_real_repository(real_repo)
3355
# Give the real branch the remote repository to let fast-pathing
3357
self._real_branch.repository = self.repository
3358
if self._lock_mode == 'r':
3359
self._real_branch.lock_read()
3360
elif self._lock_mode == 'w':
3361
self._real_branch.lock_write(token=self._lock_token)
3363
def _translate_error(self, err, **context):
3364
self.repository._translate_error(err, branch=self, **context)
3366
def _clear_cached_state(self):
3367
super(RemoteBranch, self)._clear_cached_state()
3368
if self._real_branch is not None:
3369
self._real_branch._clear_cached_state()
3371
def _clear_cached_state_of_remote_branch_only(self):
3372
"""Like _clear_cached_state, but doesn't clear the cache of
3375
This is useful when falling back to calling a method of
3376
self._real_branch that changes state. In that case the underlying
3377
branch changes, so we need to invalidate this RemoteBranch's cache of
3378
it. However, there's no need to invalidate the _real_branch's cache
3379
too, in fact doing so might harm performance.
3381
super(RemoteBranch, self)._clear_cached_state()
3384
def control_files(self):
3385
# Defer actually creating RemoteBranchLockableFiles until its needed,
3386
# because it triggers an _ensure_real that we otherwise might not need.
3387
if self._control_files is None:
3388
self._control_files = RemoteBranchLockableFiles(
3389
self.bzrdir, self._client)
3390
return self._control_files
3392
def get_physical_lock_status(self):
3393
"""See Branch.get_physical_lock_status()."""
3395
response = self._client.call('Branch.get_physical_lock_status',
3396
self._remote_path())
3397
except errors.UnknownSmartMethod:
3399
return self._real_branch.get_physical_lock_status()
3400
if response[0] not in ('yes', 'no'):
3401
raise errors.UnexpectedSmartServerResponse(response)
3402
return (response[0] == 'yes')
3404
def get_stacked_on_url(self):
3405
"""Get the URL this branch is stacked against.
3407
:raises NotStacked: If the branch is not stacked.
3408
:raises UnstackableBranchFormat: If the branch does not support
3410
:raises UnstackableRepositoryFormat: If the repository does not support
3414
# there may not be a repository yet, so we can't use
3415
# self._translate_error, so we can't use self._call either.
3416
response = self._client.call('Branch.get_stacked_on_url',
3417
self._remote_path())
3418
except errors.ErrorFromSmartServer, err:
3419
# there may not be a repository yet, so we can't call through
3420
# its _translate_error
3421
_translate_error(err, branch=self)
3422
except errors.UnknownSmartMethod, err:
3424
return self._real_branch.get_stacked_on_url()
3425
if response[0] != 'ok':
3426
raise errors.UnexpectedSmartServerResponse(response)
3429
def set_stacked_on_url(self, url):
3430
branch.Branch.set_stacked_on_url(self, url)
3432
self._is_stacked = False
3434
self._is_stacked = True
3436
def _vfs_get_tags_bytes(self):
3438
return self._real_branch._get_tags_bytes()
3441
def _get_tags_bytes(self):
3442
if self._tags_bytes is None:
3443
self._tags_bytes = self._get_tags_bytes_via_hpss()
3444
return self._tags_bytes
3446
def _get_tags_bytes_via_hpss(self):
3447
medium = self._client._medium
3448
if medium._is_remote_before((1, 13)):
3449
return self._vfs_get_tags_bytes()
3451
response = self._call('Branch.get_tags_bytes', self._remote_path())
3452
except errors.UnknownSmartMethod:
3453
medium._remember_remote_is_before((1, 13))
3454
return self._vfs_get_tags_bytes()
3457
def _vfs_set_tags_bytes(self, bytes):
3459
return self._real_branch._set_tags_bytes(bytes)
3461
def _set_tags_bytes(self, bytes):
3462
if self.is_locked():
3463
self._tags_bytes = bytes
3464
medium = self._client._medium
3465
if medium._is_remote_before((1, 18)):
3466
self._vfs_set_tags_bytes(bytes)
3470
self._remote_path(), self._lock_token, self._repo_lock_token)
3471
response = self._call_with_body_bytes(
3472
'Branch.set_tags_bytes', args, bytes)
3473
except errors.UnknownSmartMethod:
3474
medium._remember_remote_is_before((1, 18))
3475
self._vfs_set_tags_bytes(bytes)
3477
def lock_read(self):
3478
"""Lock the branch for read operations.
3480
:return: A bzrlib.lock.LogicalLockResult.
3482
self.repository.lock_read()
3483
if not self._lock_mode:
3484
self._note_lock('r')
3485
self._lock_mode = 'r'
3486
self._lock_count = 1
3487
if self._real_branch is not None:
3488
self._real_branch.lock_read()
3490
self._lock_count += 1
3491
return lock.LogicalLockResult(self.unlock)
3493
def _remote_lock_write(self, token):
3495
branch_token = repo_token = ''
3497
branch_token = token
3498
repo_token = self.repository.lock_write().repository_token
3499
self.repository.unlock()
3500
err_context = {'token': token}
3502
response = self._call(
3503
'Branch.lock_write', self._remote_path(), branch_token,
3504
repo_token or '', **err_context)
3505
except errors.LockContention, e:
3506
# The LockContention from the server doesn't have any
3507
# information about the lock_url. We re-raise LockContention
3508
# with valid lock_url.
3509
raise errors.LockContention('(remote lock)',
3510
self.repository.base.split('.bzr/')[0])
3511
if response[0] != 'ok':
3512
raise errors.UnexpectedSmartServerResponse(response)
3513
ok, branch_token, repo_token = response
3514
return branch_token, repo_token
3516
def lock_write(self, token=None):
3517
if not self._lock_mode:
3518
self._note_lock('w')
3519
# Lock the branch and repo in one remote call.
3520
remote_tokens = self._remote_lock_write(token)
3521
self._lock_token, self._repo_lock_token = remote_tokens
3522
if not self._lock_token:
3523
raise SmartProtocolError('Remote server did not return a token!')
3524
# Tell the self.repository object that it is locked.
3525
self.repository.lock_write(
3526
self._repo_lock_token, _skip_rpc=True)
3528
if self._real_branch is not None:
3529
self._real_branch.lock_write(token=self._lock_token)
3530
if token is not None:
3531
self._leave_lock = True
3533
self._leave_lock = False
3534
self._lock_mode = 'w'
3535
self._lock_count = 1
3536
elif self._lock_mode == 'r':
3537
raise errors.ReadOnlyError(self)
3539
if token is not None:
3540
# A token was given to lock_write, and we're relocking, so
3541
# check that the given token actually matches the one we
3543
if token != self._lock_token:
3544
raise errors.TokenMismatch(token, self._lock_token)
3545
self._lock_count += 1
3546
# Re-lock the repository too.
3547
self.repository.lock_write(self._repo_lock_token)
3548
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3550
def _unlock(self, branch_token, repo_token):
3551
err_context = {'token': str((branch_token, repo_token))}
3552
response = self._call(
3553
'Branch.unlock', self._remote_path(), branch_token,
3554
repo_token or '', **err_context)
3555
if response == ('ok',):
3557
raise errors.UnexpectedSmartServerResponse(response)
3559
@only_raises(errors.LockNotHeld, errors.LockBroken)
3562
self._lock_count -= 1
3563
if not self._lock_count:
3564
self._clear_cached_state()
3565
mode = self._lock_mode
3566
self._lock_mode = None
3567
if self._real_branch is not None:
3568
if (not self._leave_lock and mode == 'w' and
3569
self._repo_lock_token):
3570
# If this RemoteBranch will remove the physical lock
3571
# for the repository, make sure the _real_branch
3572
# doesn't do it first. (Because the _real_branch's
3573
# repository is set to be the RemoteRepository.)
3574
self._real_branch.repository.leave_lock_in_place()
3575
self._real_branch.unlock()
3577
# Only write-locked branched need to make a remote method
3578
# call to perform the unlock.
3580
if not self._lock_token:
3581
raise AssertionError('Locked, but no token!')
3582
branch_token = self._lock_token
3583
repo_token = self._repo_lock_token
3584
self._lock_token = None
3585
self._repo_lock_token = None
3586
if not self._leave_lock:
3587
self._unlock(branch_token, repo_token)
3589
self.repository.unlock()
3591
def break_lock(self):
3593
response = self._call(
3594
'Branch.break_lock', self._remote_path())
3595
except errors.UnknownSmartMethod:
3597
return self._real_branch.break_lock()
3598
if response != ('ok',):
3599
raise errors.UnexpectedSmartServerResponse(response)
3601
def leave_lock_in_place(self):
3602
if not self._lock_token:
3603
raise NotImplementedError(self.leave_lock_in_place)
3604
self._leave_lock = True
3606
def dont_leave_lock_in_place(self):
3607
if not self._lock_token:
3608
raise NotImplementedError(self.dont_leave_lock_in_place)
3609
self._leave_lock = False
3612
def get_rev_id(self, revno, history=None):
3614
return _mod_revision.NULL_REVISION
3615
last_revision_info = self.last_revision_info()
3616
ok, result = self.repository.get_rev_id_for_revno(
3617
revno, last_revision_info)
3620
missing_parent = result[1]
3621
# Either the revision named by the server is missing, or its parent
3622
# is. Call get_parent_map to determine which, so that we report a
3624
parent_map = self.repository.get_parent_map([missing_parent])
3625
if missing_parent in parent_map:
3626
missing_parent = parent_map[missing_parent]
3627
raise errors.RevisionNotPresent(missing_parent, self.repository)
3629
def _read_last_revision_info(self):
3630
response = self._call('Branch.last_revision_info', self._remote_path())
3631
if response[0] != 'ok':
3632
raise SmartProtocolError('unexpected response code %s' % (response,))
3633
revno = int(response[1])
3634
last_revision = response[2]
3635
return (revno, last_revision)
3637
def _gen_revision_history(self):
3638
"""See Branch._gen_revision_history()."""
3639
if self._is_stacked:
3641
return self._real_branch._gen_revision_history()
3642
response_tuple, response_handler = self._call_expecting_body(
3643
'Branch.revision_history', self._remote_path())
3644
if response_tuple[0] != 'ok':
3645
raise errors.UnexpectedSmartServerResponse(response_tuple)
3646
result = response_handler.read_body_bytes().split('\x00')
3651
def _remote_path(self):
3652
return self.bzrdir._path_for_remote_call(self._client)
3654
def _set_last_revision_descendant(self, revision_id, other_branch,
3655
allow_diverged=False, allow_overwrite_descendant=False):
3656
# This performs additional work to meet the hook contract; while its
3657
# undesirable, we have to synthesise the revno to call the hook, and
3658
# not calling the hook is worse as it means changes can't be prevented.
3659
# Having calculated this though, we can't just call into
3660
# set_last_revision_info as a simple call, because there is a set_rh
3661
# hook that some folk may still be using.
3662
old_revno, old_revid = self.last_revision_info()
3663
history = self._lefthand_history(revision_id)
3664
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3665
err_context = {'other_branch': other_branch}
3666
response = self._call('Branch.set_last_revision_ex',
3667
self._remote_path(), self._lock_token, self._repo_lock_token,
3668
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3670
self._clear_cached_state()
3671
if len(response) != 3 and response[0] != 'ok':
3672
raise errors.UnexpectedSmartServerResponse(response)
3673
new_revno, new_revision_id = response[1:]
3674
self._last_revision_info_cache = new_revno, new_revision_id
3675
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3676
if self._real_branch is not None:
3677
cache = new_revno, new_revision_id
3678
self._real_branch._last_revision_info_cache = cache
3680
def _set_last_revision(self, revision_id):
3681
old_revno, old_revid = self.last_revision_info()
3682
# This performs additional work to meet the hook contract; while its
3683
# undesirable, we have to synthesise the revno to call the hook, and
3684
# not calling the hook is worse as it means changes can't be prevented.
3685
# Having calculated this though, we can't just call into
3686
# set_last_revision_info as a simple call, because there is a set_rh
3687
# hook that some folk may still be using.
3688
history = self._lefthand_history(revision_id)
3689
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3690
self._clear_cached_state()
3691
response = self._call('Branch.set_last_revision',
3692
self._remote_path(), self._lock_token, self._repo_lock_token,
3694
if response != ('ok',):
3695
raise errors.UnexpectedSmartServerResponse(response)
3696
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3698
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3700
def set_revision_history(self, rev_history):
3701
"""See Branch.set_revision_history."""
3702
self._set_revision_history(rev_history)
3705
def _set_revision_history(self, rev_history):
3706
# Send just the tip revision of the history; the server will generate
3707
# the full history from that. If the revision doesn't exist in this
3708
# branch, NoSuchRevision will be raised.
3709
if rev_history == []:
3712
rev_id = rev_history[-1]
3713
self._set_last_revision(rev_id)
3714
for hook in branch.Branch.hooks['set_rh']:
3715
hook(self, rev_history)
3716
self._cache_revision_history(rev_history)
3718
def _get_parent_location(self):
3719
medium = self._client._medium
3720
if medium._is_remote_before((1, 13)):
3721
return self._vfs_get_parent_location()
3723
response = self._call('Branch.get_parent', self._remote_path())
3724
except errors.UnknownSmartMethod:
3725
medium._remember_remote_is_before((1, 13))
3726
return self._vfs_get_parent_location()
3727
if len(response) != 1:
3728
raise errors.UnexpectedSmartServerResponse(response)
3729
parent_location = response[0]
3730
if parent_location == '':
3732
return parent_location
3734
def _vfs_get_parent_location(self):
3736
return self._real_branch._get_parent_location()
3738
def _set_parent_location(self, url):
3739
medium = self._client._medium
3740
if medium._is_remote_before((1, 15)):
3741
return self._vfs_set_parent_location(url)
3743
call_url = url or ''
3744
if type(call_url) is not str:
3745
raise AssertionError('url must be a str or None (%s)' % url)
3746
response = self._call('Branch.set_parent_location',
3747
self._remote_path(), self._lock_token, self._repo_lock_token,
3749
except errors.UnknownSmartMethod:
3750
medium._remember_remote_is_before((1, 15))
3751
return self._vfs_set_parent_location(url)
3753
raise errors.UnexpectedSmartServerResponse(response)
3755
def _vfs_set_parent_location(self, url):
3757
return self._real_branch._set_parent_location(url)
3760
def pull(self, source, overwrite=False, stop_revision=None,
3762
self._clear_cached_state_of_remote_branch_only()
3764
return self._real_branch.pull(
3765
source, overwrite=overwrite, stop_revision=stop_revision,
3766
_override_hook_target=self, **kwargs)
3769
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3771
return self._real_branch.push(
3772
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3773
_override_hook_source_branch=self)
3775
def is_locked(self):
3776
return self._lock_count >= 1
3779
def revision_id_to_dotted_revno(self, revision_id):
3780
"""Given a revision id, return its dotted revno.
3782
:return: a tuple like (1,) or (400,1,3).
3785
response = self._call('Branch.revision_id_to_revno',
3786
self._remote_path(), revision_id)
3787
except errors.UnknownSmartMethod:
3789
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3790
if response[0] == 'ok':
3791
return tuple([int(x) for x in response[1:]])
3793
raise errors.UnexpectedSmartServerResponse(response)
3796
def revision_id_to_revno(self, revision_id):
3797
"""Given a revision id on the branch mainline, return its revno.
3802
response = self._call('Branch.revision_id_to_revno',
3803
self._remote_path(), revision_id)
3804
except errors.UnknownSmartMethod:
3806
return self._real_branch.revision_id_to_revno(revision_id)
3807
if response[0] == 'ok':
3808
if len(response) == 2:
3809
return int(response[1])
3810
raise NoSuchRevision(self, revision_id)
3812
raise errors.UnexpectedSmartServerResponse(response)
3815
def set_last_revision_info(self, revno, revision_id):
3816
# XXX: These should be returned by the set_last_revision_info verb
3817
old_revno, old_revid = self.last_revision_info()
3818
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3819
if not revision_id or not isinstance(revision_id, basestring):
3820
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3822
response = self._call('Branch.set_last_revision_info',
3823
self._remote_path(), self._lock_token, self._repo_lock_token,
3824
str(revno), revision_id)
3825
except errors.UnknownSmartMethod:
3827
self._clear_cached_state_of_remote_branch_only()
3828
self._real_branch.set_last_revision_info(revno, revision_id)
3829
self._last_revision_info_cache = revno, revision_id
3831
if response == ('ok',):
3832
self._clear_cached_state()
3833
self._last_revision_info_cache = revno, revision_id
3834
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3835
# Update the _real_branch's cache too.
3836
if self._real_branch is not None:
3837
cache = self._last_revision_info_cache
3838
self._real_branch._last_revision_info_cache = cache
3840
raise errors.UnexpectedSmartServerResponse(response)
3843
def generate_revision_history(self, revision_id, last_rev=None,
3845
medium = self._client._medium
3846
if not medium._is_remote_before((1, 6)):
3847
# Use a smart method for 1.6 and above servers
3849
self._set_last_revision_descendant(revision_id, other_branch,
3850
allow_diverged=True, allow_overwrite_descendant=True)
3852
except errors.UnknownSmartMethod:
3853
medium._remember_remote_is_before((1, 6))
3854
self._clear_cached_state_of_remote_branch_only()
3855
self._set_revision_history(self._lefthand_history(revision_id,
3856
last_rev=last_rev,other_branch=other_branch))
3858
def set_push_location(self, location):
3860
return self._real_branch.set_push_location(location)
3862
def heads_to_fetch(self):
3863
if self._format._use_default_local_heads_to_fetch():
3864
# We recognise this format, and its heads-to-fetch implementation
3865
# is the default one (tip + tags). In this case it's cheaper to
3866
# just use the default implementation rather than a special RPC as
3867
# the tip and tags data is cached.
3868
return branch.Branch.heads_to_fetch(self)
3869
medium = self._client._medium
3870
if medium._is_remote_before((2, 4)):
3871
return self._vfs_heads_to_fetch()
3873
return self._rpc_heads_to_fetch()
3874
except errors.UnknownSmartMethod:
3875
medium._remember_remote_is_before((2, 4))
3876
return self._vfs_heads_to_fetch()
3878
def _rpc_heads_to_fetch(self):
3879
response = self._call('Branch.heads_to_fetch', self._remote_path())
3880
if len(response) != 2:
3881
raise errors.UnexpectedSmartServerResponse(response)
3882
must_fetch, if_present_fetch = response
3883
return set(must_fetch), set(if_present_fetch)
3885
def _vfs_heads_to_fetch(self):
3887
return self._real_branch.heads_to_fetch()
3890
class RemoteConfig(object):
3891
"""A Config that reads and writes from smart verbs.
3893
It is a low-level object that considers config data to be name/value pairs
3894
that may be associated with a section. Assigning meaning to the these
3895
values is done at higher levels like bzrlib.config.TreeConfig.
3898
def get_option(self, name, section=None, default=None):
3899
"""Return the value associated with a named option.
3901
:param name: The name of the value
3902
:param section: The section the option is in (if any)
3903
:param default: The value to return if the value is not set
3904
:return: The value or default value
3907
configobj = self._get_configobj()
3910
section_obj = configobj
3913
section_obj = configobj[section]
3916
if section_obj is None:
3919
value = section_obj.get(name, default)
3920
except errors.UnknownSmartMethod:
3921
value = self._vfs_get_option(name, section, default)
3922
for hook in config.OldConfigHooks['get']:
3923
hook(self, name, value)
3926
def _response_to_configobj(self, response):
3927
if len(response[0]) and response[0][0] != 'ok':
3928
raise errors.UnexpectedSmartServerResponse(response)
3929
lines = response[1].read_body_bytes().splitlines()
3930
conf = config.ConfigObj(lines, encoding='utf-8')
3931
for hook in config.OldConfigHooks['load']:
3936
class RemoteBranchConfig(RemoteConfig):
3937
"""A RemoteConfig for Branches."""
3939
def __init__(self, branch):
3940
self._branch = branch
3942
def _get_configobj(self):
3943
path = self._branch._remote_path()
3944
response = self._branch._client.call_expecting_body(
3945
'Branch.get_config_file', path)
3946
return self._response_to_configobj(response)
3948
def set_option(self, value, name, section=None):
3949
"""Set the value associated with a named option.
3951
:param value: The value to set
3952
:param name: The name of the value to set
3953
:param section: The section the option is in (if any)
3955
medium = self._branch._client._medium
3956
if medium._is_remote_before((1, 14)):
3957
return self._vfs_set_option(value, name, section)
3958
if isinstance(value, dict):
3959
if medium._is_remote_before((2, 2)):
3960
return self._vfs_set_option(value, name, section)
3961
return self._set_config_option_dict(value, name, section)
3963
return self._set_config_option(value, name, section)
3965
def _set_config_option(self, value, name, section):
3967
path = self._branch._remote_path()
3968
response = self._branch._client.call('Branch.set_config_option',
3969
path, self._branch._lock_token, self._branch._repo_lock_token,
3970
value.encode('utf8'), name, section or '')
3971
except errors.UnknownSmartMethod:
3972
medium = self._branch._client._medium
3973
medium._remember_remote_is_before((1, 14))
3974
return self._vfs_set_option(value, name, section)
3976
raise errors.UnexpectedSmartServerResponse(response)
3978
def _serialize_option_dict(self, option_dict):
3980
for key, value in option_dict.items():
3981
if isinstance(key, unicode):
3982
key = key.encode('utf8')
3983
if isinstance(value, unicode):
3984
value = value.encode('utf8')
3985
utf8_dict[key] = value
3986
return bencode.bencode(utf8_dict)
3988
def _set_config_option_dict(self, value, name, section):
3990
path = self._branch._remote_path()
3991
serialised_dict = self._serialize_option_dict(value)
3992
response = self._branch._client.call(
3993
'Branch.set_config_option_dict',
3994
path, self._branch._lock_token, self._branch._repo_lock_token,
3995
serialised_dict, name, section or '')
3996
except errors.UnknownSmartMethod:
3997
medium = self._branch._client._medium
3998
medium._remember_remote_is_before((2, 2))
3999
return self._vfs_set_option(value, name, section)
4001
raise errors.UnexpectedSmartServerResponse(response)
4003
def _real_object(self):
4004
self._branch._ensure_real()
4005
return self._branch._real_branch
4007
def _vfs_set_option(self, value, name, section=None):
4008
return self._real_object()._get_config().set_option(
4009
value, name, section)
4012
class RemoteBzrDirConfig(RemoteConfig):
4013
"""A RemoteConfig for BzrDirs."""
4015
def __init__(self, bzrdir):
4016
self._bzrdir = bzrdir
4018
def _get_configobj(self):
4019
medium = self._bzrdir._client._medium
4020
verb = 'BzrDir.get_config_file'
4021
if medium._is_remote_before((1, 15)):
4022
raise errors.UnknownSmartMethod(verb)
4023
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4024
response = self._bzrdir._call_expecting_body(
4026
return self._response_to_configobj(response)
4028
def _vfs_get_option(self, name, section, default):
4029
return self._real_object()._get_config().get_option(
4030
name, section, default)
4032
def set_option(self, value, name, section=None):
4033
"""Set the value associated with a named option.
4035
:param value: The value to set
4036
:param name: The name of the value to set
4037
:param section: The section the option is in (if any)
4039
return self._real_object()._get_config().set_option(
4040
value, name, section)
4042
def _real_object(self):
4043
self._bzrdir._ensure_real()
4044
return self._bzrdir._real_bzrdir
4047
def _extract_tar(tar, to_dir):
4048
"""Extract all the contents of a tarfile object.
4050
A replacement for extractall, which is not present in python2.4
4053
tar.extract(tarinfo, to_dir)
4056
error_translators = registry.Registry()
4057
no_context_error_translators = registry.Registry()
4060
def _translate_error(err, **context):
4061
"""Translate an ErrorFromSmartServer into a more useful error.
4063
Possible context keys:
4071
If the error from the server doesn't match a known pattern, then
4072
UnknownErrorFromSmartServer is raised.
4076
return context[name]
4077
except KeyError, key_err:
4078
mutter('Missing key %r in context %r', key_err.args[0], context)
4081
"""Get the path from the context if present, otherwise use first error
4085
return context['path']
4086
except KeyError, key_err:
4088
return err.error_args[0]
4089
except IndexError, idx_err:
4091
'Missing key %r in context %r', key_err.args[0], context)
4095
translator = error_translators.get(err.error_verb)
4099
raise translator(err, find, get_path)
4101
translator = no_context_error_translators.get(err.error_verb)
4103
raise errors.UnknownErrorFromSmartServer(err)
4105
raise translator(err)
4108
error_translators.register('NoSuchRevision',
4109
lambda err, find, get_path: NoSuchRevision(
4110
find('branch'), err.error_args[0]))
4111
error_translators.register('nosuchrevision',
4112
lambda err, find, get_path: NoSuchRevision(
4113
find('repository'), err.error_args[0]))
4115
def _translate_nobranch_error(err, find, get_path):
4116
if len(err.error_args) >= 1:
4117
extra = err.error_args[0]
4120
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4123
error_translators.register('nobranch', _translate_nobranch_error)
4124
error_translators.register('norepository',
4125
lambda err, find, get_path: errors.NoRepositoryPresent(
4127
error_translators.register('UnlockableTransport',
4128
lambda err, find, get_path: errors.UnlockableTransport(
4129
find('bzrdir').root_transport))
4130
error_translators.register('TokenMismatch',
4131
lambda err, find, get_path: errors.TokenMismatch(
4132
find('token'), '(remote token)'))
4133
error_translators.register('Diverged',
4134
lambda err, find, get_path: errors.DivergedBranches(
4135
find('branch'), find('other_branch')))
4136
error_translators.register('NotStacked',
4137
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4139
def _translate_PermissionDenied(err, find, get_path):
4141
if len(err.error_args) >= 2:
4142
extra = err.error_args[1]
4145
return errors.PermissionDenied(path, extra=extra)
4147
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4148
error_translators.register('ReadError',
4149
lambda err, find, get_path: errors.ReadError(get_path()))
4150
error_translators.register('NoSuchFile',
4151
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4152
error_translators.register('TokenLockingNotSupported',
4153
lambda err, find, get_path: errors.TokenLockingNotSupported(
4154
find('repository')))
4155
error_translators.register('UnsuspendableWriteGroup',
4156
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4157
repository=find('repository')))
4158
error_translators.register('UnresumableWriteGroup',
4159
lambda err, find, get_path: errors.UnresumableWriteGroup(
4160
repository=find('repository'), write_groups=err.error_args[0],
4161
reason=err.error_args[1]))
4162
no_context_error_translators.register('IncompatibleRepositories',
4163
lambda err: errors.IncompatibleRepositories(
4164
err.error_args[0], err.error_args[1], err.error_args[2]))
4165
no_context_error_translators.register('LockContention',
4166
lambda err: errors.LockContention('(remote lock)'))
4167
no_context_error_translators.register('LockFailed',
4168
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4169
no_context_error_translators.register('TipChangeRejected',
4170
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4171
no_context_error_translators.register('UnstackableBranchFormat',
4172
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4173
no_context_error_translators.register('UnstackableRepositoryFormat',
4174
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4175
no_context_error_translators.register('FileExists',
4176
lambda err: errors.FileExists(err.error_args[0]))
4177
no_context_error_translators.register('DirectoryNotEmpty',
4178
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4180
def _translate_short_readv_error(err):
4181
args = err.error_args
4182
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4185
no_context_error_translators.register('ShortReadvError',
4186
_translate_short_readv_error)
4188
def _translate_unicode_error(err):
4189
encoding = str(err.error_args[0]) # encoding must always be a string
4190
val = err.error_args[1]
4191
start = int(err.error_args[2])
4192
end = int(err.error_args[3])
4193
reason = str(err.error_args[4]) # reason must always be a string
4194
if val.startswith('u:'):
4195
val = val[2:].decode('utf-8')
4196
elif val.startswith('s:'):
4197
val = val[2:].decode('base64')
4198
if err.error_verb == 'UnicodeDecodeError':
4199
raise UnicodeDecodeError(encoding, val, start, end, reason)
4200
elif err.error_verb == 'UnicodeEncodeError':
4201
raise UnicodeEncodeError(encoding, val, start, end, reason)
4203
no_context_error_translators.register('UnicodeEncodeError',
4204
_translate_unicode_error)
4205
no_context_error_translators.register('UnicodeDecodeError',
4206
_translate_unicode_error)
4207
no_context_error_translators.register('ReadOnlyError',
4208
lambda err: errors.TransportNotPossible('readonly transport'))
4209
no_context_error_translators.register('MemoryError',
4210
lambda err: errors.BzrError("remote server out of memory\n"
4211
"Retry non-remotely, or contact the server admin for details."))
4212
no_context_error_translators.register('RevisionNotPresent',
4213
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4215
no_context_error_translators.register('BzrCheckError',
4216
lambda err: errors.BzrCheckError(msg=err.error_args[0]))