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
23
bzrdir as _mod_bzrdir,
34
repository as _mod_repository,
35
revision as _mod_revision,
38
testament as _mod_testament,
43
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
44
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
45
from bzrlib.errors import (
49
from bzrlib.i18n import gettext
50
from bzrlib.inventory import Inventory
51
from bzrlib.lockable_files import LockableFiles
52
from bzrlib.smart import client, vfs, repository as smart_repo
53
from bzrlib.smart.client import _SmartClient
54
from bzrlib.revision import NULL_REVISION
55
from bzrlib.revisiontree import InventoryRevisionTree
56
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
57
from bzrlib.serializer import format_registry as serializer_format_registry
58
from bzrlib.trace import mutter, note, warning, log_exception_quietly
61
_DEFAULT_SEARCH_DEPTH = 100
64
class _RpcHelper(object):
65
"""Mixin class that helps with issuing RPCs."""
67
def _call(self, method, *args, **err_context):
69
return self._client.call(method, *args)
70
except errors.ErrorFromSmartServer, err:
71
self._translate_error(err, **err_context)
73
def _call_expecting_body(self, method, *args, **err_context):
75
return self._client.call_expecting_body(method, *args)
76
except errors.ErrorFromSmartServer, err:
77
self._translate_error(err, **err_context)
79
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
81
return self._client.call_with_body_bytes(method, args, body_bytes)
82
except errors.ErrorFromSmartServer, err:
83
self._translate_error(err, **err_context)
85
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
88
return self._client.call_with_body_bytes_expecting_body(
89
method, args, body_bytes)
90
except errors.ErrorFromSmartServer, err:
91
self._translate_error(err, **err_context)
94
def response_tuple_to_repo_format(response):
95
"""Convert a response tuple describing a repository format to a format."""
96
format = RemoteRepositoryFormat()
97
format._rich_root_data = (response[0] == 'yes')
98
format._supports_tree_reference = (response[1] == 'yes')
99
format._supports_external_lookups = (response[2] == 'yes')
100
format._network_name = response[3]
104
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
105
# does not have to be imported unless a remote format is involved.
107
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
108
"""Format representing bzrdirs accessed via a smart server"""
110
supports_workingtrees = False
113
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
114
# XXX: It's a bit ugly that the network name is here, because we'd
115
# like to believe that format objects are stateless or at least
116
# immutable, However, we do at least avoid mutating the name after
117
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
118
self._network_name = None
121
return "%s(_network_name=%r)" % (self.__class__.__name__,
124
def get_format_description(self):
125
if self._network_name:
127
real_format = controldir.network_format_registry.get(
132
return 'Remote: ' + real_format.get_format_description()
133
return 'bzr remote bzrdir'
135
def get_format_string(self):
136
raise NotImplementedError(self.get_format_string)
138
def network_name(self):
139
if self._network_name:
140
return self._network_name
142
raise AssertionError("No network name set.")
144
def initialize_on_transport(self, transport):
146
# hand off the request to the smart server
147
client_medium = transport.get_smart_medium()
148
except errors.NoSmartMedium:
149
# TODO: lookup the local format from a server hint.
150
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
151
return local_dir_format.initialize_on_transport(transport)
152
client = _SmartClient(client_medium)
153
path = client.remote_path_from_transport(transport)
155
response = client.call('BzrDirFormat.initialize', path)
156
except errors.ErrorFromSmartServer, err:
157
_translate_error(err, path=path)
158
if response[0] != 'ok':
159
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
160
format = RemoteBzrDirFormat()
161
self._supply_sub_formats_to(format)
162
return RemoteBzrDir(transport, format)
164
def parse_NoneTrueFalse(self, arg):
171
raise AssertionError("invalid arg %r" % arg)
173
def _serialize_NoneTrueFalse(self, arg):
180
def _serialize_NoneString(self, arg):
183
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
184
create_prefix=False, force_new_repo=False, stacked_on=None,
185
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
188
# hand off the request to the smart server
189
client_medium = transport.get_smart_medium()
190
except errors.NoSmartMedium:
193
# Decline to open it if the server doesn't support our required
194
# version (3) so that the VFS-based transport will do it.
195
if client_medium.should_probe():
197
server_version = client_medium.protocol_version()
198
if server_version != '2':
202
except errors.SmartProtocolError:
203
# Apparently there's no usable smart server there, even though
204
# the medium supports the smart protocol.
209
client = _SmartClient(client_medium)
210
path = client.remote_path_from_transport(transport)
211
if client_medium._is_remote_before((1, 16)):
214
# TODO: lookup the local format from a server hint.
215
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
216
self._supply_sub_formats_to(local_dir_format)
217
return local_dir_format.initialize_on_transport_ex(transport,
218
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
219
force_new_repo=force_new_repo, stacked_on=stacked_on,
220
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
221
make_working_trees=make_working_trees, shared_repo=shared_repo,
223
return self._initialize_on_transport_ex_rpc(client, path, transport,
224
use_existing_dir, create_prefix, force_new_repo, stacked_on,
225
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
227
def _initialize_on_transport_ex_rpc(self, client, path, transport,
228
use_existing_dir, create_prefix, force_new_repo, stacked_on,
229
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
231
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
232
args.append(self._serialize_NoneTrueFalse(create_prefix))
233
args.append(self._serialize_NoneTrueFalse(force_new_repo))
234
args.append(self._serialize_NoneString(stacked_on))
235
# stack_on_pwd is often/usually our transport
238
stack_on_pwd = transport.relpath(stack_on_pwd)
241
except errors.PathNotChild:
243
args.append(self._serialize_NoneString(stack_on_pwd))
244
args.append(self._serialize_NoneString(repo_format_name))
245
args.append(self._serialize_NoneTrueFalse(make_working_trees))
246
args.append(self._serialize_NoneTrueFalse(shared_repo))
247
request_network_name = self._network_name or \
248
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
250
response = client.call('BzrDirFormat.initialize_ex_1.16',
251
request_network_name, path, *args)
252
except errors.UnknownSmartMethod:
253
client._medium._remember_remote_is_before((1,16))
254
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
255
self._supply_sub_formats_to(local_dir_format)
256
return local_dir_format.initialize_on_transport_ex(transport,
257
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
258
force_new_repo=force_new_repo, stacked_on=stacked_on,
259
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
260
make_working_trees=make_working_trees, shared_repo=shared_repo,
262
except errors.ErrorFromSmartServer, err:
263
_translate_error(err, path=path)
264
repo_path = response[0]
265
bzrdir_name = response[6]
266
require_stacking = response[7]
267
require_stacking = self.parse_NoneTrueFalse(require_stacking)
268
format = RemoteBzrDirFormat()
269
format._network_name = bzrdir_name
270
self._supply_sub_formats_to(format)
271
bzrdir = RemoteBzrDir(transport, format, _client=client)
273
repo_format = response_tuple_to_repo_format(response[1:])
277
repo_bzrdir_format = RemoteBzrDirFormat()
278
repo_bzrdir_format._network_name = response[5]
279
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
283
final_stack = response[8] or None
284
final_stack_pwd = response[9] or None
286
final_stack_pwd = urlutils.join(
287
transport.base, final_stack_pwd)
288
remote_repo = RemoteRepository(repo_bzr, repo_format)
289
if len(response) > 10:
290
# Updated server verb that locks remotely.
291
repo_lock_token = response[10] or None
292
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
294
remote_repo.dont_leave_lock_in_place()
296
remote_repo.lock_write()
297
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
298
final_stack_pwd, require_stacking)
299
policy.acquire_repository()
303
bzrdir._format.set_branch_format(self.get_branch_format())
305
# The repo has already been created, but we need to make sure that
306
# we'll make a stackable branch.
307
bzrdir._format.require_stacking(_skip_repo=True)
308
return remote_repo, bzrdir, require_stacking, policy
310
def _open(self, transport):
311
return RemoteBzrDir(transport, self)
313
def __eq__(self, other):
314
if not isinstance(other, RemoteBzrDirFormat):
316
return self.get_format_description() == other.get_format_description()
318
def __return_repository_format(self):
319
# Always return a RemoteRepositoryFormat object, but if a specific bzr
320
# repository format has been asked for, tell the RemoteRepositoryFormat
321
# that it should use that for init() etc.
322
result = RemoteRepositoryFormat()
323
custom_format = getattr(self, '_repository_format', None)
325
if isinstance(custom_format, RemoteRepositoryFormat):
328
# We will use the custom format to create repositories over the
329
# wire; expose its details like rich_root_data for code to
331
result._custom_format = custom_format
334
def get_branch_format(self):
335
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
336
if not isinstance(result, RemoteBranchFormat):
337
new_result = RemoteBranchFormat()
338
new_result._custom_format = result
340
self.set_branch_format(new_result)
344
repository_format = property(__return_repository_format,
345
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
348
class RemoteControlStore(config.IniFileStore):
349
"""Control store which attempts to use HPSS calls to retrieve control store.
351
Note that this is specific to bzr-based formats.
354
def __init__(self, bzrdir):
355
super(RemoteControlStore, self).__init__()
357
self._real_store = None
359
def lock_write(self, token=None):
361
return self._real_store.lock_write(token)
365
return self._real_store.unlock()
369
# We need to be able to override the undecorated implementation
370
self.save_without_locking()
372
def save_without_locking(self):
373
super(RemoteControlStore, self).save()
375
def _ensure_real(self):
376
self.bzrdir._ensure_real()
377
if self._real_store is None:
378
self._real_store = config.ControlStore(self.bzrdir)
380
def external_url(self):
381
return self.bzrdir.user_url
383
def _load_content(self):
384
medium = self.bzrdir._client._medium
385
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
387
response, handler = self.bzrdir._call_expecting_body(
388
'BzrDir.get_config_file', path)
389
except errors.UnknownSmartMethod:
391
return self._real_store._load_content()
392
if len(response) and response[0] != 'ok':
393
raise errors.UnexpectedSmartServerResponse(response)
394
return handler.read_body_bytes()
396
def _save_content(self, content):
397
# FIXME JRV 2011-11-22: Ideally this should use a
398
# HPSS call too, but at the moment it is not possible
399
# to write lock control directories.
401
return self._real_store._save_content(content)
404
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
405
"""Control directory on a remote server, accessed via bzr:// or similar."""
407
def __init__(self, transport, format, _client=None, _force_probe=False):
408
"""Construct a RemoteBzrDir.
410
:param _client: Private parameter for testing. Disables probing and the
411
use of a real bzrdir.
413
_mod_bzrdir.BzrDir.__init__(self, transport, format)
414
# this object holds a delegated bzrdir that uses file-level operations
415
# to talk to the other side
416
self._real_bzrdir = None
417
self._has_working_tree = None
418
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
419
# create_branch for details.
420
self._next_open_branch_result = None
423
medium = transport.get_smart_medium()
424
self._client = client._SmartClient(medium)
426
self._client = _client
433
return '%s(%r)' % (self.__class__.__name__, self._client)
435
def _probe_bzrdir(self):
436
medium = self._client._medium
437
path = self._path_for_remote_call(self._client)
438
if medium._is_remote_before((2, 1)):
442
self._rpc_open_2_1(path)
444
except errors.UnknownSmartMethod:
445
medium._remember_remote_is_before((2, 1))
448
def _rpc_open_2_1(self, path):
449
response = self._call('BzrDir.open_2.1', path)
450
if response == ('no',):
451
raise errors.NotBranchError(path=self.root_transport.base)
452
elif response[0] == 'yes':
453
if response[1] == 'yes':
454
self._has_working_tree = True
455
elif response[1] == 'no':
456
self._has_working_tree = False
458
raise errors.UnexpectedSmartServerResponse(response)
460
raise errors.UnexpectedSmartServerResponse(response)
462
def _rpc_open(self, path):
463
response = self._call('BzrDir.open', path)
464
if response not in [('yes',), ('no',)]:
465
raise errors.UnexpectedSmartServerResponse(response)
466
if response == ('no',):
467
raise errors.NotBranchError(path=self.root_transport.base)
469
def _ensure_real(self):
470
"""Ensure that there is a _real_bzrdir set.
472
Used before calls to self._real_bzrdir.
474
if not self._real_bzrdir:
475
if 'hpssvfs' in debug.debug_flags:
477
warning('VFS BzrDir access triggered\n%s',
478
''.join(traceback.format_stack()))
479
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
480
self.root_transport, _server_formats=False)
481
self._format._network_name = \
482
self._real_bzrdir._format.network_name()
484
def _translate_error(self, err, **context):
485
_translate_error(err, bzrdir=self, **context)
487
def break_lock(self):
488
# Prevent aliasing problems in the next_open_branch_result cache.
489
# See create_branch for rationale.
490
self._next_open_branch_result = None
491
return _mod_bzrdir.BzrDir.break_lock(self)
493
def _vfs_cloning_metadir(self, require_stacking=False):
495
return self._real_bzrdir.cloning_metadir(
496
require_stacking=require_stacking)
498
def cloning_metadir(self, require_stacking=False):
499
medium = self._client._medium
500
if medium._is_remote_before((1, 13)):
501
return self._vfs_cloning_metadir(require_stacking=require_stacking)
502
verb = 'BzrDir.cloning_metadir'
507
path = self._path_for_remote_call(self._client)
509
response = self._call(verb, path, stacking)
510
except errors.UnknownSmartMethod:
511
medium._remember_remote_is_before((1, 13))
512
return self._vfs_cloning_metadir(require_stacking=require_stacking)
513
except errors.UnknownErrorFromSmartServer, err:
514
if err.error_tuple != ('BranchReference',):
516
# We need to resolve the branch reference to determine the
517
# cloning_metadir. This causes unnecessary RPCs to open the
518
# referenced branch (and bzrdir, etc) but only when the caller
519
# didn't already resolve the branch reference.
520
referenced_branch = self.open_branch()
521
return referenced_branch.bzrdir.cloning_metadir()
522
if len(response) != 3:
523
raise errors.UnexpectedSmartServerResponse(response)
524
control_name, repo_name, branch_info = response
525
if len(branch_info) != 2:
526
raise errors.UnexpectedSmartServerResponse(response)
527
branch_ref, branch_name = branch_info
529
format = controldir.network_format_registry.get(control_name)
531
raise errors.UnknownFormatError(kind='control', format=control_name)
535
format.repository_format = _mod_repository.network_format_registry.get(
538
raise errors.UnknownFormatError(kind='repository',
540
if branch_ref == 'ref':
541
# XXX: we need possible_transports here to avoid reopening the
542
# connection to the referenced location
543
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
544
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
545
format.set_branch_format(branch_format)
546
elif branch_ref == 'branch':
549
branch_format = branch.network_format_registry.get(
552
raise errors.UnknownFormatError(kind='branch',
554
format.set_branch_format(branch_format)
556
raise errors.UnexpectedSmartServerResponse(response)
559
def create_repository(self, shared=False):
560
# as per meta1 formats - just delegate to the format object which may
562
result = self._format.repository_format.initialize(self, shared)
563
if not isinstance(result, RemoteRepository):
564
return self.open_repository()
568
def destroy_repository(self):
569
"""See BzrDir.destroy_repository"""
570
path = self._path_for_remote_call(self._client)
572
response = self._call('BzrDir.destroy_repository', path)
573
except errors.UnknownSmartMethod:
575
self._real_bzrdir.destroy_repository()
577
if response[0] != 'ok':
578
raise SmartProtocolError('unexpected response code %s' % (response,))
580
def create_branch(self, name=None, repository=None,
581
append_revisions_only=None):
582
# as per meta1 formats - just delegate to the format object which may
584
real_branch = self._format.get_branch_format().initialize(self,
585
name=name, repository=repository,
586
append_revisions_only=append_revisions_only)
587
if not isinstance(real_branch, RemoteBranch):
588
if not isinstance(repository, RemoteRepository):
589
raise AssertionError(
590
'need a RemoteRepository to use with RemoteBranch, got %r'
592
result = RemoteBranch(self, repository, real_branch, name=name)
595
# BzrDir.clone_on_transport() uses the result of create_branch but does
596
# not return it to its callers; we save approximately 8% of our round
597
# trips by handing the branch we created back to the first caller to
598
# open_branch rather than probing anew. Long term we need a API in
599
# bzrdir that doesn't discard result objects (like result_branch).
601
self._next_open_branch_result = result
604
def destroy_branch(self, name=None):
605
"""See BzrDir.destroy_branch"""
606
path = self._path_for_remote_call(self._client)
612
response = self._call('BzrDir.destroy_branch', path, *args)
613
except errors.UnknownSmartMethod:
615
self._real_bzrdir.destroy_branch(name=name)
616
self._next_open_branch_result = None
618
self._next_open_branch_result = None
619
if response[0] != 'ok':
620
raise SmartProtocolError('unexpected response code %s' % (response,))
622
def create_workingtree(self, revision_id=None, from_branch=None,
623
accelerator_tree=None, hardlink=False):
624
raise errors.NotLocalUrl(self.transport.base)
626
def find_branch_format(self, name=None):
627
"""Find the branch 'format' for this bzrdir.
629
This might be a synthetic object for e.g. RemoteBranch and SVN.
631
b = self.open_branch(name=name)
634
def get_branch_reference(self, name=None):
635
"""See BzrDir.get_branch_reference()."""
637
# XXX JRV20100304: Support opening colocated branches
638
raise errors.NoColocatedBranchSupport(self)
639
response = self._get_branch_reference()
640
if response[0] == 'ref':
645
def _get_branch_reference(self):
646
path = self._path_for_remote_call(self._client)
647
medium = self._client._medium
649
('BzrDir.open_branchV3', (2, 1)),
650
('BzrDir.open_branchV2', (1, 13)),
651
('BzrDir.open_branch', None),
653
for verb, required_version in candidate_calls:
654
if required_version and medium._is_remote_before(required_version):
657
response = self._call(verb, path)
658
except errors.UnknownSmartMethod:
659
if required_version is None:
661
medium._remember_remote_is_before(required_version)
664
if verb == 'BzrDir.open_branch':
665
if response[0] != 'ok':
666
raise errors.UnexpectedSmartServerResponse(response)
667
if response[1] != '':
668
return ('ref', response[1])
670
return ('branch', '')
671
if response[0] not in ('ref', 'branch'):
672
raise errors.UnexpectedSmartServerResponse(response)
675
def _get_tree_branch(self, name=None):
676
"""See BzrDir._get_tree_branch()."""
677
return None, self.open_branch(name=name)
679
def open_branch(self, name=None, unsupported=False,
680
ignore_fallbacks=False, possible_transports=None):
682
raise NotImplementedError('unsupported flag support not implemented yet.')
683
if self._next_open_branch_result is not None:
684
# See create_branch for details.
685
result = self._next_open_branch_result
686
self._next_open_branch_result = None
688
response = self._get_branch_reference()
689
if response[0] == 'ref':
690
# a branch reference, use the existing BranchReference logic.
691
format = BranchReferenceFormat()
692
return format.open(self, name=name, _found=True,
693
location=response[1], ignore_fallbacks=ignore_fallbacks,
694
possible_transports=possible_transports)
695
branch_format_name = response[1]
696
if not branch_format_name:
697
branch_format_name = None
698
format = RemoteBranchFormat(network_name=branch_format_name)
699
return RemoteBranch(self, self.find_repository(), format=format,
700
setup_stacking=not ignore_fallbacks, name=name,
701
possible_transports=possible_transports)
703
def _open_repo_v1(self, path):
704
verb = 'BzrDir.find_repository'
705
response = self._call(verb, path)
706
if response[0] != 'ok':
707
raise errors.UnexpectedSmartServerResponse(response)
708
# servers that only support the v1 method don't support external
711
repo = self._real_bzrdir.open_repository()
712
response = response + ('no', repo._format.network_name())
713
return response, repo
715
def _open_repo_v2(self, path):
716
verb = 'BzrDir.find_repositoryV2'
717
response = self._call(verb, path)
718
if response[0] != 'ok':
719
raise errors.UnexpectedSmartServerResponse(response)
721
repo = self._real_bzrdir.open_repository()
722
response = response + (repo._format.network_name(),)
723
return response, repo
725
def _open_repo_v3(self, path):
726
verb = 'BzrDir.find_repositoryV3'
727
medium = self._client._medium
728
if medium._is_remote_before((1, 13)):
729
raise errors.UnknownSmartMethod(verb)
731
response = self._call(verb, path)
732
except errors.UnknownSmartMethod:
733
medium._remember_remote_is_before((1, 13))
735
if response[0] != 'ok':
736
raise errors.UnexpectedSmartServerResponse(response)
737
return response, None
739
def open_repository(self):
740
path = self._path_for_remote_call(self._client)
742
for probe in [self._open_repo_v3, self._open_repo_v2,
745
response, real_repo = probe(path)
747
except errors.UnknownSmartMethod:
750
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
751
if response[0] != 'ok':
752
raise errors.UnexpectedSmartServerResponse(response)
753
if len(response) != 6:
754
raise SmartProtocolError('incorrect response length %s' % (response,))
755
if response[1] == '':
756
# repo is at this dir.
757
format = response_tuple_to_repo_format(response[2:])
758
# Used to support creating a real format instance when needed.
759
format._creating_bzrdir = self
760
remote_repo = RemoteRepository(self, format)
761
format._creating_repo = remote_repo
762
if real_repo is not None:
763
remote_repo._set_real_repository(real_repo)
766
raise errors.NoRepositoryPresent(self)
768
def has_workingtree(self):
769
if self._has_working_tree is None:
770
path = self._path_for_remote_call(self._client)
772
response = self._call('BzrDir.has_workingtree', path)
773
except errors.UnknownSmartMethod:
775
self._has_working_tree = self._real_bzrdir.has_workingtree()
777
if response[0] not in ('yes', 'no'):
778
raise SmartProtocolError('unexpected response code %s' % (response,))
779
self._has_working_tree = (response[0] == 'yes')
780
return self._has_working_tree
782
def open_workingtree(self, recommend_upgrade=True):
783
if self.has_workingtree():
784
raise errors.NotLocalUrl(self.root_transport)
786
raise errors.NoWorkingTree(self.root_transport.base)
788
def _path_for_remote_call(self, client):
789
"""Return the path to be used for this bzrdir in a remote call."""
790
return urlutils.split_segment_parameters_raw(
791
client.remote_path_from_transport(self.root_transport))[0]
793
def get_branch_transport(self, branch_format, name=None):
795
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
797
def get_repository_transport(self, repository_format):
799
return self._real_bzrdir.get_repository_transport(repository_format)
801
def get_workingtree_transport(self, workingtree_format):
803
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
805
def can_convert_format(self):
806
"""Upgrading of remote bzrdirs is not supported yet."""
809
def needs_format_conversion(self, format):
810
"""Upgrading of remote bzrdirs is not supported yet."""
813
def _get_config(self):
814
return RemoteBzrDirConfig(self)
816
def _get_config_store(self):
817
return RemoteControlStore(self)
820
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
821
"""Format for repositories accessed over a _SmartClient.
823
Instances of this repository are represented by RemoteRepository
826
The RemoteRepositoryFormat is parameterized during construction
827
to reflect the capabilities of the real, remote format. Specifically
828
the attributes rich_root_data and supports_tree_reference are set
829
on a per instance basis, and are not set (and should not be) at
832
:ivar _custom_format: If set, a specific concrete repository format that
833
will be used when initializing a repository with this
834
RemoteRepositoryFormat.
835
:ivar _creating_repo: If set, the repository object that this
836
RemoteRepositoryFormat was created for: it can be called into
837
to obtain data like the network name.
840
_matchingbzrdir = RemoteBzrDirFormat()
841
supports_full_versioned_files = True
842
supports_leaving_lock = True
845
_mod_repository.RepositoryFormat.__init__(self)
846
self._custom_format = None
847
self._network_name = None
848
self._creating_bzrdir = None
849
self._revision_graph_can_have_wrong_parents = None
850
self._supports_chks = None
851
self._supports_external_lookups = None
852
self._supports_tree_reference = None
853
self._supports_funky_characters = None
854
self._supports_nesting_repositories = None
855
self._rich_root_data = None
858
return "%s(_network_name=%r)" % (self.__class__.__name__,
862
def fast_deltas(self):
864
return self._custom_format.fast_deltas
867
def rich_root_data(self):
868
if self._rich_root_data is None:
870
self._rich_root_data = self._custom_format.rich_root_data
871
return self._rich_root_data
874
def supports_chks(self):
875
if self._supports_chks is None:
877
self._supports_chks = self._custom_format.supports_chks
878
return self._supports_chks
881
def supports_external_lookups(self):
882
if self._supports_external_lookups is None:
884
self._supports_external_lookups = \
885
self._custom_format.supports_external_lookups
886
return self._supports_external_lookups
889
def supports_funky_characters(self):
890
if self._supports_funky_characters is None:
892
self._supports_funky_characters = \
893
self._custom_format.supports_funky_characters
894
return self._supports_funky_characters
897
def supports_nesting_repositories(self):
898
if self._supports_nesting_repositories is None:
900
self._supports_nesting_repositories = \
901
self._custom_format.supports_nesting_repositories
902
return self._supports_nesting_repositories
905
def supports_tree_reference(self):
906
if self._supports_tree_reference is None:
908
self._supports_tree_reference = \
909
self._custom_format.supports_tree_reference
910
return self._supports_tree_reference
913
def revision_graph_can_have_wrong_parents(self):
914
if self._revision_graph_can_have_wrong_parents is None:
916
self._revision_graph_can_have_wrong_parents = \
917
self._custom_format.revision_graph_can_have_wrong_parents
918
return self._revision_graph_can_have_wrong_parents
920
def _vfs_initialize(self, a_bzrdir, shared):
921
"""Helper for common code in initialize."""
922
if self._custom_format:
923
# Custom format requested
924
result = self._custom_format.initialize(a_bzrdir, shared=shared)
925
elif self._creating_bzrdir is not None:
926
# Use the format that the repository we were created to back
928
prior_repo = self._creating_bzrdir.open_repository()
929
prior_repo._ensure_real()
930
result = prior_repo._real_repository._format.initialize(
931
a_bzrdir, shared=shared)
933
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
934
# support remote initialization.
935
# We delegate to a real object at this point (as RemoteBzrDir
936
# delegate to the repository format which would lead to infinite
937
# recursion if we just called a_bzrdir.create_repository.
938
a_bzrdir._ensure_real()
939
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
940
if not isinstance(result, RemoteRepository):
941
return self.open(a_bzrdir)
945
def initialize(self, a_bzrdir, shared=False):
946
# Being asked to create on a non RemoteBzrDir:
947
if not isinstance(a_bzrdir, RemoteBzrDir):
948
return self._vfs_initialize(a_bzrdir, shared)
949
medium = a_bzrdir._client._medium
950
if medium._is_remote_before((1, 13)):
951
return self._vfs_initialize(a_bzrdir, shared)
952
# Creating on a remote bzr dir.
953
# 1) get the network name to use.
954
if self._custom_format:
955
network_name = self._custom_format.network_name()
956
elif self._network_name:
957
network_name = self._network_name
959
# Select the current bzrlib default and ask for that.
960
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
961
reference_format = reference_bzrdir_format.repository_format
962
network_name = reference_format.network_name()
963
# 2) try direct creation via RPC
964
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
965
verb = 'BzrDir.create_repository'
971
response = a_bzrdir._call(verb, path, network_name, shared_str)
972
except errors.UnknownSmartMethod:
973
# Fallback - use vfs methods
974
medium._remember_remote_is_before((1, 13))
975
return self._vfs_initialize(a_bzrdir, shared)
977
# Turn the response into a RemoteRepository object.
978
format = response_tuple_to_repo_format(response[1:])
979
# Used to support creating a real format instance when needed.
980
format._creating_bzrdir = a_bzrdir
981
remote_repo = RemoteRepository(a_bzrdir, format)
982
format._creating_repo = remote_repo
985
def open(self, a_bzrdir):
986
if not isinstance(a_bzrdir, RemoteBzrDir):
987
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
988
return a_bzrdir.open_repository()
990
def _ensure_real(self):
991
if self._custom_format is None:
993
self._custom_format = _mod_repository.network_format_registry.get(
996
raise errors.UnknownFormatError(kind='repository',
997
format=self._network_name)
1000
def _fetch_order(self):
1002
return self._custom_format._fetch_order
1005
def _fetch_uses_deltas(self):
1007
return self._custom_format._fetch_uses_deltas
1010
def _fetch_reconcile(self):
1012
return self._custom_format._fetch_reconcile
1014
def get_format_description(self):
1016
return 'Remote: ' + self._custom_format.get_format_description()
1018
def __eq__(self, other):
1019
return self.__class__ is other.__class__
1021
def network_name(self):
1022
if self._network_name:
1023
return self._network_name
1024
self._creating_repo._ensure_real()
1025
return self._creating_repo._real_repository._format.network_name()
1028
def pack_compresses(self):
1030
return self._custom_format.pack_compresses
1033
def _serializer(self):
1035
return self._custom_format._serializer
1038
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1039
lock._RelockDebugMixin):
1040
"""Repository accessed over rpc.
1042
For the moment most operations are performed using local transport-backed
1046
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1047
"""Create a RemoteRepository instance.
1049
:param remote_bzrdir: The bzrdir hosting this repository.
1050
:param format: The RemoteFormat object to use.
1051
:param real_repository: If not None, a local implementation of the
1052
repository logic for the repository, usually accessing the data
1054
:param _client: Private testing parameter - override the smart client
1055
to be used by the repository.
1058
self._real_repository = real_repository
1060
self._real_repository = None
1061
self.bzrdir = remote_bzrdir
1063
self._client = remote_bzrdir._client
1065
self._client = _client
1066
self._format = format
1067
self._lock_mode = None
1068
self._lock_token = None
1069
self._write_group_tokens = None
1070
self._lock_count = 0
1071
self._leave_lock = False
1072
# Cache of revision parents; misses are cached during read locks, and
1073
# write locks when no _real_repository has been set.
1074
self._unstacked_provider = graph.CachingParentsProvider(
1075
get_parent_map=self._get_parent_map_rpc)
1076
self._unstacked_provider.disable_cache()
1078
# These depend on the actual remote format, so force them off for
1079
# maximum compatibility. XXX: In future these should depend on the
1080
# remote repository instance, but this is irrelevant until we perform
1081
# reconcile via an RPC call.
1082
self._reconcile_does_inventory_gc = False
1083
self._reconcile_fixes_text_parents = False
1084
self._reconcile_backsup_inventory = False
1085
self.base = self.bzrdir.transport.base
1086
# Additional places to query for data.
1087
self._fallback_repositories = []
1090
def user_transport(self):
1091
return self.bzrdir.user_transport
1094
def control_transport(self):
1095
# XXX: Normally you shouldn't directly get at the remote repository
1096
# transport, but I'm not sure it's worth making this method
1097
# optional -- mbp 2010-04-21
1098
return self.bzrdir.get_repository_transport(None)
1101
return "%s(%s)" % (self.__class__.__name__, self.base)
1105
def abort_write_group(self, suppress_errors=False):
1106
"""Complete a write group on the decorated repository.
1108
Smart methods perform operations in a single step so this API
1109
is not really applicable except as a compatibility thunk
1110
for older plugins that don't use e.g. the CommitBuilder
1113
:param suppress_errors: see Repository.abort_write_group.
1115
if self._real_repository:
1117
return self._real_repository.abort_write_group(
1118
suppress_errors=suppress_errors)
1119
if not self.is_in_write_group():
1121
mutter('(suppressed) not in write group')
1123
raise errors.BzrError("not in write group")
1124
path = self.bzrdir._path_for_remote_call(self._client)
1126
response = self._call('Repository.abort_write_group', path,
1127
self._lock_token, self._write_group_tokens)
1128
except Exception, exc:
1129
self._write_group = None
1130
if not suppress_errors:
1132
mutter('abort_write_group failed')
1133
log_exception_quietly()
1134
note(gettext('bzr: ERROR (ignored): %s'), exc)
1136
if response != ('ok', ):
1137
raise errors.UnexpectedSmartServerResponse(response)
1138
self._write_group_tokens = None
1141
def chk_bytes(self):
1142
"""Decorate the real repository for now.
1144
In the long term a full blown network facility is needed to avoid
1145
creating a real repository object locally.
1148
return self._real_repository.chk_bytes
1150
def commit_write_group(self):
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
if self._real_repository:
1160
return self._real_repository.commit_write_group()
1161
if not self.is_in_write_group():
1162
raise errors.BzrError("not in write group")
1163
path = self.bzrdir._path_for_remote_call(self._client)
1164
response = self._call('Repository.commit_write_group', path,
1165
self._lock_token, self._write_group_tokens)
1166
if response != ('ok', ):
1167
raise errors.UnexpectedSmartServerResponse(response)
1168
self._write_group_tokens = None
1170
def resume_write_group(self, tokens):
1171
if self._real_repository:
1172
return self._real_repository.resume_write_group(tokens)
1173
path = self.bzrdir._path_for_remote_call(self._client)
1175
response = self._call('Repository.check_write_group', path,
1176
self._lock_token, tokens)
1177
except errors.UnknownSmartMethod:
1179
return self._real_repository.resume_write_group(tokens)
1180
if response != ('ok', ):
1181
raise errors.UnexpectedSmartServerResponse(response)
1182
self._write_group_tokens = tokens
1184
def suspend_write_group(self):
1185
if self._real_repository:
1186
return self._real_repository.suspend_write_group()
1187
ret = self._write_group_tokens or []
1188
self._write_group_tokens = None
1191
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1193
return self._real_repository.get_missing_parent_inventories(
1194
check_for_missing_texts=check_for_missing_texts)
1196
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1198
return self._real_repository.get_rev_id_for_revno(
1201
def get_rev_id_for_revno(self, revno, known_pair):
1202
"""See Repository.get_rev_id_for_revno."""
1203
path = self.bzrdir._path_for_remote_call(self._client)
1205
if self._client._medium._is_remote_before((1, 17)):
1206
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1207
response = self._call(
1208
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1209
except errors.UnknownSmartMethod:
1210
self._client._medium._remember_remote_is_before((1, 17))
1211
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1212
if response[0] == 'ok':
1213
return True, response[1]
1214
elif response[0] == 'history-incomplete':
1215
known_pair = response[1:3]
1216
for fallback in self._fallback_repositories:
1217
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1222
# Not found in any fallbacks
1223
return False, known_pair
1225
raise errors.UnexpectedSmartServerResponse(response)
1227
def _ensure_real(self):
1228
"""Ensure that there is a _real_repository set.
1230
Used before calls to self._real_repository.
1232
Note that _ensure_real causes many roundtrips to the server which are
1233
not desirable, and prevents the use of smart one-roundtrip RPC's to
1234
perform complex operations (such as accessing parent data, streaming
1235
revisions etc). Adding calls to _ensure_real should only be done when
1236
bringing up new functionality, adding fallbacks for smart methods that
1237
require a fallback path, and never to replace an existing smart method
1238
invocation. If in doubt chat to the bzr network team.
1240
if self._real_repository is None:
1241
if 'hpssvfs' in debug.debug_flags:
1243
warning('VFS Repository access triggered\n%s',
1244
''.join(traceback.format_stack()))
1245
self._unstacked_provider.missing_keys.clear()
1246
self.bzrdir._ensure_real()
1247
self._set_real_repository(
1248
self.bzrdir._real_bzrdir.open_repository())
1250
def _translate_error(self, err, **context):
1251
self.bzrdir._translate_error(err, repository=self, **context)
1253
def find_text_key_references(self):
1254
"""Find the text key references within the repository.
1256
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1257
to whether they were referred to by the inventory of the
1258
revision_id that they contain. The inventory texts from all present
1259
revision ids are assessed to generate this report.
1262
return self._real_repository.find_text_key_references()
1264
def _generate_text_key_index(self):
1265
"""Generate a new text key index for the repository.
1267
This is an expensive function that will take considerable time to run.
1269
:return: A dict mapping (file_id, revision_id) tuples to a list of
1270
parents, also (file_id, revision_id) tuples.
1273
return self._real_repository._generate_text_key_index()
1275
def _get_revision_graph(self, revision_id):
1276
"""Private method for using with old (< 1.2) servers to fallback."""
1277
if revision_id is None:
1279
elif _mod_revision.is_null(revision_id):
1282
path = self.bzrdir._path_for_remote_call(self._client)
1283
response = self._call_expecting_body(
1284
'Repository.get_revision_graph', path, revision_id)
1285
response_tuple, response_handler = response
1286
if response_tuple[0] != 'ok':
1287
raise errors.UnexpectedSmartServerResponse(response_tuple)
1288
coded = response_handler.read_body_bytes()
1290
# no revisions in this repository!
1292
lines = coded.split('\n')
1295
d = tuple(line.split())
1296
revision_graph[d[0]] = d[1:]
1298
return revision_graph
1300
def _get_sink(self):
1301
"""See Repository._get_sink()."""
1302
return RemoteStreamSink(self)
1304
def _get_source(self, to_format):
1305
"""Return a source for streaming from this repository."""
1306
return RemoteStreamSource(self, to_format)
1309
def get_file_graph(self):
1310
return graph.Graph(self.texts)
1313
def has_revision(self, revision_id):
1314
"""True if this repository has a copy of the revision."""
1315
# Copy of bzrlib.repository.Repository.has_revision
1316
return revision_id in self.has_revisions((revision_id,))
1319
def has_revisions(self, revision_ids):
1320
"""Probe to find out the presence of multiple revisions.
1322
:param revision_ids: An iterable of revision_ids.
1323
:return: A set of the revision_ids that were present.
1325
# Copy of bzrlib.repository.Repository.has_revisions
1326
parent_map = self.get_parent_map(revision_ids)
1327
result = set(parent_map)
1328
if _mod_revision.NULL_REVISION in revision_ids:
1329
result.add(_mod_revision.NULL_REVISION)
1332
def _has_same_fallbacks(self, other_repo):
1333
"""Returns true if the repositories have the same fallbacks."""
1334
# XXX: copied from Repository; it should be unified into a base class
1335
# <https://bugs.launchpad.net/bzr/+bug/401622>
1336
my_fb = self._fallback_repositories
1337
other_fb = other_repo._fallback_repositories
1338
if len(my_fb) != len(other_fb):
1340
for f, g in zip(my_fb, other_fb):
1341
if not f.has_same_location(g):
1345
def has_same_location(self, other):
1346
# TODO: Move to RepositoryBase and unify with the regular Repository
1347
# one; unfortunately the tests rely on slightly different behaviour at
1348
# present -- mbp 20090710
1349
return (self.__class__ is other.__class__ and
1350
self.bzrdir.transport.base == other.bzrdir.transport.base)
1352
def get_graph(self, other_repository=None):
1353
"""Return the graph for this repository format"""
1354
parents_provider = self._make_parents_provider(other_repository)
1355
return graph.Graph(parents_provider)
1358
def get_known_graph_ancestry(self, revision_ids):
1359
"""Return the known graph for a set of revision ids and their ancestors.
1361
st = static_tuple.StaticTuple
1362
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1363
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1364
return graph.GraphThunkIdsToKeys(known_graph)
1366
def gather_stats(self, revid=None, committers=None):
1367
"""See Repository.gather_stats()."""
1368
path = self.bzrdir._path_for_remote_call(self._client)
1369
# revid can be None to indicate no revisions, not just NULL_REVISION
1370
if revid is None or _mod_revision.is_null(revid):
1374
if committers is None or not committers:
1375
fmt_committers = 'no'
1377
fmt_committers = 'yes'
1378
response_tuple, response_handler = self._call_expecting_body(
1379
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1380
if response_tuple[0] != 'ok':
1381
raise errors.UnexpectedSmartServerResponse(response_tuple)
1383
body = response_handler.read_body_bytes()
1385
for line in body.split('\n'):
1388
key, val_text = line.split(':')
1389
if key in ('revisions', 'size', 'committers'):
1390
result[key] = int(val_text)
1391
elif key in ('firstrev', 'latestrev'):
1392
values = val_text.split(' ')[1:]
1393
result[key] = (float(values[0]), long(values[1]))
1397
def find_branches(self, using=False):
1398
"""See Repository.find_branches()."""
1399
# should be an API call to the server.
1401
return self._real_repository.find_branches(using=using)
1403
def get_physical_lock_status(self):
1404
"""See Repository.get_physical_lock_status()."""
1405
path = self.bzrdir._path_for_remote_call(self._client)
1407
response = self._call('Repository.get_physical_lock_status', path)
1408
except errors.UnknownSmartMethod:
1410
return self._real_repository.get_physical_lock_status()
1411
if response[0] not in ('yes', 'no'):
1412
raise errors.UnexpectedSmartServerResponse(response)
1413
return (response[0] == 'yes')
1415
def is_in_write_group(self):
1416
"""Return True if there is an open write group.
1418
write groups are only applicable locally for the smart server..
1420
if self._write_group_tokens is not None:
1422
if self._real_repository:
1423
return self._real_repository.is_in_write_group()
1425
def is_locked(self):
1426
return self._lock_count >= 1
1428
def is_shared(self):
1429
"""See Repository.is_shared()."""
1430
path = self.bzrdir._path_for_remote_call(self._client)
1431
response = self._call('Repository.is_shared', path)
1432
if response[0] not in ('yes', 'no'):
1433
raise SmartProtocolError('unexpected response code %s' % (response,))
1434
return response[0] == 'yes'
1436
def is_write_locked(self):
1437
return self._lock_mode == 'w'
1439
def _warn_if_deprecated(self, branch=None):
1440
# If we have a real repository, the check will be done there, if we
1441
# don't the check will be done remotely.
1444
def lock_read(self):
1445
"""Lock the repository for read operations.
1447
:return: A bzrlib.lock.LogicalLockResult.
1449
# wrong eventually - want a local lock cache context
1450
if not self._lock_mode:
1451
self._note_lock('r')
1452
self._lock_mode = 'r'
1453
self._lock_count = 1
1454
self._unstacked_provider.enable_cache(cache_misses=True)
1455
if self._real_repository is not None:
1456
self._real_repository.lock_read()
1457
for repo in self._fallback_repositories:
1460
self._lock_count += 1
1461
return lock.LogicalLockResult(self.unlock)
1463
def _remote_lock_write(self, token):
1464
path = self.bzrdir._path_for_remote_call(self._client)
1467
err_context = {'token': token}
1468
response = self._call('Repository.lock_write', path, token,
1470
if response[0] == 'ok':
1471
ok, token = response
1474
raise errors.UnexpectedSmartServerResponse(response)
1476
def lock_write(self, token=None, _skip_rpc=False):
1477
if not self._lock_mode:
1478
self._note_lock('w')
1480
if self._lock_token is not None:
1481
if token != self._lock_token:
1482
raise errors.TokenMismatch(token, self._lock_token)
1483
self._lock_token = token
1485
self._lock_token = self._remote_lock_write(token)
1486
# if self._lock_token is None, then this is something like packs or
1487
# svn where we don't get to lock the repo, or a weave style repository
1488
# where we cannot lock it over the wire and attempts to do so will
1490
if self._real_repository is not None:
1491
self._real_repository.lock_write(token=self._lock_token)
1492
if token is not None:
1493
self._leave_lock = True
1495
self._leave_lock = False
1496
self._lock_mode = 'w'
1497
self._lock_count = 1
1498
cache_misses = self._real_repository is None
1499
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1500
for repo in self._fallback_repositories:
1501
# Writes don't affect fallback repos
1503
elif self._lock_mode == 'r':
1504
raise errors.ReadOnlyError(self)
1506
self._lock_count += 1
1507
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1509
def leave_lock_in_place(self):
1510
if not self._lock_token:
1511
raise NotImplementedError(self.leave_lock_in_place)
1512
self._leave_lock = True
1514
def dont_leave_lock_in_place(self):
1515
if not self._lock_token:
1516
raise NotImplementedError(self.dont_leave_lock_in_place)
1517
self._leave_lock = False
1519
def _set_real_repository(self, repository):
1520
"""Set the _real_repository for this repository.
1522
:param repository: The repository to fallback to for non-hpss
1523
implemented operations.
1525
if self._real_repository is not None:
1526
# Replacing an already set real repository.
1527
# We cannot do this [currently] if the repository is locked -
1528
# synchronised state might be lost.
1529
if self.is_locked():
1530
raise AssertionError('_real_repository is already set')
1531
if isinstance(repository, RemoteRepository):
1532
raise AssertionError()
1533
self._real_repository = repository
1534
# three code paths happen here:
1535
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1536
# up stacking. In this case self._fallback_repositories is [], and the
1537
# real repo is already setup. Preserve the real repo and
1538
# RemoteRepository.add_fallback_repository will avoid adding
1540
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1541
# ensure_real is triggered from a branch, the real repository to
1542
# set already has a matching list with separate instances, but
1543
# as they are also RemoteRepositories we don't worry about making the
1544
# lists be identical.
1545
# 3) new servers, RemoteRepository.ensure_real is triggered before
1546
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1547
# and need to populate it.
1548
if (self._fallback_repositories and
1549
len(self._real_repository._fallback_repositories) !=
1550
len(self._fallback_repositories)):
1551
if len(self._real_repository._fallback_repositories):
1552
raise AssertionError(
1553
"cannot cleanly remove existing _fallback_repositories")
1554
for fb in self._fallback_repositories:
1555
self._real_repository.add_fallback_repository(fb)
1556
if self._lock_mode == 'w':
1557
# if we are already locked, the real repository must be able to
1558
# acquire the lock with our token.
1559
self._real_repository.lock_write(self._lock_token)
1560
elif self._lock_mode == 'r':
1561
self._real_repository.lock_read()
1562
if self._write_group_tokens is not None:
1563
# if we are already in a write group, resume it
1564
self._real_repository.resume_write_group(self._write_group_tokens)
1565
self._write_group_tokens = None
1567
def start_write_group(self):
1568
"""Start a write group on the decorated repository.
1570
Smart methods perform operations in a single step so this API
1571
is not really applicable except as a compatibility thunk
1572
for older plugins that don't use e.g. the CommitBuilder
1575
if self._real_repository:
1577
return self._real_repository.start_write_group()
1578
if not self.is_write_locked():
1579
raise errors.NotWriteLocked(self)
1580
if self._write_group_tokens is not None:
1581
raise errors.BzrError('already in a write group')
1582
path = self.bzrdir._path_for_remote_call(self._client)
1584
response = self._call('Repository.start_write_group', path,
1586
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1588
return self._real_repository.start_write_group()
1589
if response[0] != 'ok':
1590
raise errors.UnexpectedSmartServerResponse(response)
1591
self._write_group_tokens = response[1]
1593
def _unlock(self, token):
1594
path = self.bzrdir._path_for_remote_call(self._client)
1596
# with no token the remote repository is not persistently locked.
1598
err_context = {'token': token}
1599
response = self._call('Repository.unlock', path, token,
1601
if response == ('ok',):
1604
raise errors.UnexpectedSmartServerResponse(response)
1606
@only_raises(errors.LockNotHeld, errors.LockBroken)
1608
if not self._lock_count:
1609
return lock.cant_unlock_not_held(self)
1610
self._lock_count -= 1
1611
if self._lock_count > 0:
1613
self._unstacked_provider.disable_cache()
1614
old_mode = self._lock_mode
1615
self._lock_mode = None
1617
# The real repository is responsible at present for raising an
1618
# exception if it's in an unfinished write group. However, it
1619
# normally will *not* actually remove the lock from disk - that's
1620
# done by the server on receiving the Repository.unlock call.
1621
# This is just to let the _real_repository stay up to date.
1622
if self._real_repository is not None:
1623
self._real_repository.unlock()
1624
elif self._write_group_tokens is not None:
1625
self.abort_write_group()
1627
# The rpc-level lock should be released even if there was a
1628
# problem releasing the vfs-based lock.
1630
# Only write-locked repositories need to make a remote method
1631
# call to perform the unlock.
1632
old_token = self._lock_token
1633
self._lock_token = None
1634
if not self._leave_lock:
1635
self._unlock(old_token)
1636
# Fallbacks are always 'lock_read()' so we don't pay attention to
1638
for repo in self._fallback_repositories:
1641
def break_lock(self):
1642
# should hand off to the network
1643
path = self.bzrdir._path_for_remote_call(self._client)
1645
response = self._call("Repository.break_lock", path)
1646
except errors.UnknownSmartMethod:
1648
return self._real_repository.break_lock()
1649
if response != ('ok',):
1650
raise errors.UnexpectedSmartServerResponse(response)
1652
def _get_tarball(self, compression):
1653
"""Return a TemporaryFile containing a repository tarball.
1655
Returns None if the server does not support sending tarballs.
1658
path = self.bzrdir._path_for_remote_call(self._client)
1660
response, protocol = self._call_expecting_body(
1661
'Repository.tarball', path, compression)
1662
except errors.UnknownSmartMethod:
1663
protocol.cancel_read_body()
1665
if response[0] == 'ok':
1666
# Extract the tarball and return it
1667
t = tempfile.NamedTemporaryFile()
1668
# TODO: rpc layer should read directly into it...
1669
t.write(protocol.read_body_bytes())
1672
raise errors.UnexpectedSmartServerResponse(response)
1675
def sprout(self, to_bzrdir, revision_id=None):
1676
"""Create a descendent repository for new development.
1678
Unlike clone, this does not copy the settings of the repository.
1680
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1681
dest_repo.fetch(self, revision_id=revision_id)
1684
def _create_sprouting_repo(self, a_bzrdir, shared):
1685
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1686
# use target default format.
1687
dest_repo = a_bzrdir.create_repository()
1689
# Most control formats need the repository to be specifically
1690
# created, but on some old all-in-one formats it's not needed
1692
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1693
except errors.UninitializableFormat:
1694
dest_repo = a_bzrdir.open_repository()
1697
### These methods are just thin shims to the VFS object for now.
1700
def revision_tree(self, revision_id):
1701
revision_id = _mod_revision.ensure_null(revision_id)
1702
if revision_id == _mod_revision.NULL_REVISION:
1703
return InventoryRevisionTree(self,
1704
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1706
return list(self.revision_trees([revision_id]))[0]
1708
def get_serializer_format(self):
1709
path = self.bzrdir._path_for_remote_call(self._client)
1711
response = self._call('VersionedFileRepository.get_serializer_format',
1713
except errors.UnknownSmartMethod:
1715
return self._real_repository.get_serializer_format()
1716
if response[0] != 'ok':
1717
raise errors.UnexpectedSmartServerResponse(response)
1720
def get_commit_builder(self, branch, parents, config, timestamp=None,
1721
timezone=None, committer=None, revprops=None,
1722
revision_id=None, lossy=False):
1723
# FIXME: It ought to be possible to call this without immediately
1724
# triggering _ensure_real. For now it's the easiest thing to do.
1726
real_repo = self._real_repository
1727
builder = real_repo.get_commit_builder(branch, parents,
1728
config, timestamp=timestamp, timezone=timezone,
1729
committer=committer, revprops=revprops,
1730
revision_id=revision_id, lossy=lossy)
1733
def add_fallback_repository(self, repository):
1734
"""Add a repository to use for looking up data not held locally.
1736
:param repository: A repository.
1738
if not self._format.supports_external_lookups:
1739
raise errors.UnstackableRepositoryFormat(
1740
self._format.network_name(), self.base)
1741
# We need to accumulate additional repositories here, to pass them in
1744
# Make the check before we lock: this raises an exception.
1745
self._check_fallback_repository(repository)
1746
if self.is_locked():
1747
# We will call fallback.unlock() when we transition to the unlocked
1748
# state, so always add a lock here. If a caller passes us a locked
1749
# repository, they are responsible for unlocking it later.
1750
repository.lock_read()
1751
self._fallback_repositories.append(repository)
1752
# If self._real_repository was parameterised already (e.g. because a
1753
# _real_branch had its get_stacked_on_url method called), then the
1754
# repository to be added may already be in the _real_repositories list.
1755
if self._real_repository is not None:
1756
fallback_locations = [repo.user_url for repo in
1757
self._real_repository._fallback_repositories]
1758
if repository.user_url not in fallback_locations:
1759
self._real_repository.add_fallback_repository(repository)
1761
def _check_fallback_repository(self, repository):
1762
"""Check that this repository can fallback to repository safely.
1764
Raise an error if not.
1766
:param repository: A repository to fallback to.
1768
return _mod_repository.InterRepository._assert_same_model(
1771
def add_inventory(self, revid, inv, parents):
1773
return self._real_repository.add_inventory(revid, inv, parents)
1775
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1776
parents, basis_inv=None, propagate_caches=False):
1778
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1779
delta, new_revision_id, parents, basis_inv=basis_inv,
1780
propagate_caches=propagate_caches)
1782
def add_revision(self, rev_id, rev, inv=None, config=None):
1784
return self._real_repository.add_revision(
1785
rev_id, rev, inv=inv, config=config)
1788
def get_inventory(self, revision_id):
1789
return list(self.iter_inventories([revision_id]))[0]
1791
def iter_inventories(self, revision_ids, ordering=None):
1793
return self._real_repository.iter_inventories(revision_ids, ordering)
1796
def get_revision(self, revision_id):
1797
return self.get_revisions([revision_id])[0]
1799
def get_transaction(self):
1801
return self._real_repository.get_transaction()
1804
def clone(self, a_bzrdir, revision_id=None):
1805
dest_repo = self._create_sprouting_repo(
1806
a_bzrdir, shared=self.is_shared())
1807
self.copy_content_into(dest_repo, revision_id)
1810
def make_working_trees(self):
1811
"""See Repository.make_working_trees"""
1812
path = self.bzrdir._path_for_remote_call(self._client)
1814
response = self._call('Repository.make_working_trees', path)
1815
except errors.UnknownSmartMethod:
1817
return self._real_repository.make_working_trees()
1818
if response[0] not in ('yes', 'no'):
1819
raise SmartProtocolError('unexpected response code %s' % (response,))
1820
return response[0] == 'yes'
1822
def refresh_data(self):
1823
"""Re-read any data needed to synchronise with disk.
1825
This method is intended to be called after another repository instance
1826
(such as one used by a smart server) has inserted data into the
1827
repository. On all repositories this will work outside of write groups.
1828
Some repository formats (pack and newer for bzrlib native formats)
1829
support refresh_data inside write groups. If called inside a write
1830
group on a repository that does not support refreshing in a write group
1831
IsInWriteGroupError will be raised.
1833
if self._real_repository is not None:
1834
self._real_repository.refresh_data()
1836
def revision_ids_to_search_result(self, result_set):
1837
"""Convert a set of revision ids to a graph SearchResult."""
1838
result_parents = set()
1839
for parents in self.get_graph().get_parent_map(
1840
result_set).itervalues():
1841
result_parents.update(parents)
1842
included_keys = result_set.intersection(result_parents)
1843
start_keys = result_set.difference(included_keys)
1844
exclude_keys = result_parents.difference(result_set)
1845
result = vf_search.SearchResult(start_keys, exclude_keys,
1846
len(result_set), result_set)
1850
def search_missing_revision_ids(self, other,
1851
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1852
find_ghosts=True, revision_ids=None, if_present_ids=None,
1854
"""Return the revision ids that other has that this does not.
1856
These are returned in topological order.
1858
revision_id: only return revision ids included by revision_id.
1860
if symbol_versioning.deprecated_passed(revision_id):
1861
symbol_versioning.warn(
1862
'search_missing_revision_ids(revision_id=...) was '
1863
'deprecated in 2.4. Use revision_ids=[...] instead.',
1864
DeprecationWarning, stacklevel=2)
1865
if revision_ids is not None:
1866
raise AssertionError(
1867
'revision_ids is mutually exclusive with revision_id')
1868
if revision_id is not None:
1869
revision_ids = [revision_id]
1870
inter_repo = _mod_repository.InterRepository.get(other, self)
1871
return inter_repo.search_missing_revision_ids(
1872
find_ghosts=find_ghosts, revision_ids=revision_ids,
1873
if_present_ids=if_present_ids, limit=limit)
1875
def fetch(self, source, revision_id=None, find_ghosts=False,
1877
# No base implementation to use as RemoteRepository is not a subclass
1878
# of Repository; so this is a copy of Repository.fetch().
1879
if fetch_spec is not None and revision_id is not None:
1880
raise AssertionError(
1881
"fetch_spec and revision_id are mutually exclusive.")
1882
if self.is_in_write_group():
1883
raise errors.InternalBzrError(
1884
"May not fetch while in a write group.")
1885
# fast path same-url fetch operations
1886
if (self.has_same_location(source)
1887
and fetch_spec is None
1888
and self._has_same_fallbacks(source)):
1889
# check that last_revision is in 'from' and then return a
1891
if (revision_id is not None and
1892
not _mod_revision.is_null(revision_id)):
1893
self.get_revision(revision_id)
1895
# if there is no specific appropriate InterRepository, this will get
1896
# the InterRepository base class, which raises an
1897
# IncompatibleRepositories when asked to fetch.
1898
inter = _mod_repository.InterRepository.get(source, self)
1899
if (fetch_spec is not None and
1900
not getattr(inter, "supports_fetch_spec", False)):
1901
raise errors.UnsupportedOperation(
1902
"fetch_spec not supported for %r" % inter)
1903
return inter.fetch(revision_id=revision_id,
1904
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1906
def create_bundle(self, target, base, fileobj, format=None):
1908
self._real_repository.create_bundle(target, base, fileobj, format)
1911
@symbol_versioning.deprecated_method(
1912
symbol_versioning.deprecated_in((2, 4, 0)))
1913
def get_ancestry(self, revision_id, topo_sorted=True):
1915
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1917
def fileids_altered_by_revision_ids(self, revision_ids):
1919
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1921
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1923
return self._real_repository._get_versioned_file_checker(
1924
revisions, revision_versions_cache)
1926
def _iter_files_bytes_rpc(self, desired_files, absent):
1927
path = self.bzrdir._path_for_remote_call(self._client)
1930
for (file_id, revid, identifier) in desired_files:
1931
lines.append("%s\0%s" % (
1932
osutils.safe_file_id(file_id),
1933
osutils.safe_revision_id(revid)))
1934
identifiers.append(identifier)
1935
(response_tuple, response_handler) = (
1936
self._call_with_body_bytes_expecting_body(
1937
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
1938
if response_tuple != ('ok', ):
1939
response_handler.cancel_read_body()
1940
raise errors.UnexpectedSmartServerResponse(response_tuple)
1941
byte_stream = response_handler.read_streamed_body()
1942
def decompress_stream(start, byte_stream, unused):
1943
decompressor = zlib.decompressobj()
1944
yield decompressor.decompress(start)
1945
while decompressor.unused_data == "":
1947
data = byte_stream.next()
1948
except StopIteration:
1950
yield decompressor.decompress(data)
1951
yield decompressor.flush()
1952
unused.append(decompressor.unused_data)
1955
while not "\n" in unused:
1956
unused += byte_stream.next()
1957
header, rest = unused.split("\n", 1)
1958
args = header.split("\0")
1959
if args[0] == "absent":
1960
absent[identifiers[int(args[3])]] = (args[1], args[2])
1963
elif args[0] == "ok":
1966
raise errors.UnexpectedSmartServerResponse(args)
1968
yield (identifiers[idx],
1969
decompress_stream(rest, byte_stream, unused_chunks))
1970
unused = "".join(unused_chunks)
1972
def iter_files_bytes(self, desired_files):
1973
"""See Repository.iter_file_bytes.
1977
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
1978
desired_files, absent):
1979
yield identifier, bytes_iterator
1980
for fallback in self._fallback_repositories:
1983
desired_files = [(key[0], key[1], identifier) for
1984
(identifier, key) in absent.iteritems()]
1985
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
1986
del absent[identifier]
1987
yield identifier, bytes_iterator
1989
# There may be more missing items, but raise an exception
1991
missing_identifier = absent.keys()[0]
1992
missing_key = absent[missing_identifier]
1993
raise errors.RevisionNotPresent(revision_id=missing_key[1],
1994
file_id=missing_key[0])
1995
except errors.UnknownSmartMethod:
1997
for (identifier, bytes_iterator) in (
1998
self._real_repository.iter_files_bytes(desired_files)):
1999
yield identifier, bytes_iterator
2001
def get_cached_parent_map(self, revision_ids):
2002
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2003
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2005
def get_parent_map(self, revision_ids):
2006
"""See bzrlib.Graph.get_parent_map()."""
2007
return self._make_parents_provider().get_parent_map(revision_ids)
2009
def _get_parent_map_rpc(self, keys):
2010
"""Helper for get_parent_map that performs the RPC."""
2011
medium = self._client._medium
2012
if medium._is_remote_before((1, 2)):
2013
# We already found out that the server can't understand
2014
# Repository.get_parent_map requests, so just fetch the whole
2017
# Note that this reads the whole graph, when only some keys are
2018
# wanted. On this old server there's no way (?) to get them all
2019
# in one go, and the user probably will have seen a warning about
2020
# the server being old anyhow.
2021
rg = self._get_revision_graph(None)
2022
# There is an API discrepancy between get_parent_map and
2023
# get_revision_graph. Specifically, a "key:()" pair in
2024
# get_revision_graph just means a node has no parents. For
2025
# "get_parent_map" it means the node is a ghost. So fix up the
2026
# graph to correct this.
2027
# https://bugs.launchpad.net/bzr/+bug/214894
2028
# There is one other "bug" which is that ghosts in
2029
# get_revision_graph() are not returned at all. But we won't worry
2030
# about that for now.
2031
for node_id, parent_ids in rg.iteritems():
2032
if parent_ids == ():
2033
rg[node_id] = (NULL_REVISION,)
2034
rg[NULL_REVISION] = ()
2039
raise ValueError('get_parent_map(None) is not valid')
2040
if NULL_REVISION in keys:
2041
keys.discard(NULL_REVISION)
2042
found_parents = {NULL_REVISION:()}
2044
return found_parents
2047
# TODO(Needs analysis): We could assume that the keys being requested
2048
# from get_parent_map are in a breadth first search, so typically they
2049
# will all be depth N from some common parent, and we don't have to
2050
# have the server iterate from the root parent, but rather from the
2051
# keys we're searching; and just tell the server the keyspace we
2052
# already have; but this may be more traffic again.
2054
# Transform self._parents_map into a search request recipe.
2055
# TODO: Manage this incrementally to avoid covering the same path
2056
# repeatedly. (The server will have to on each request, but the less
2057
# work done the better).
2059
# Negative caching notes:
2060
# new server sends missing when a request including the revid
2061
# 'include-missing:' is present in the request.
2062
# missing keys are serialised as missing:X, and we then call
2063
# provider.note_missing(X) for-all X
2064
parents_map = self._unstacked_provider.get_cached_map()
2065
if parents_map is None:
2066
# Repository is not locked, so there's no cache.
2068
if _DEFAULT_SEARCH_DEPTH <= 0:
2069
(start_set, stop_keys,
2070
key_count) = vf_search.search_result_from_parent_map(
2071
parents_map, self._unstacked_provider.missing_keys)
2073
(start_set, stop_keys,
2074
key_count) = vf_search.limited_search_result_from_parent_map(
2075
parents_map, self._unstacked_provider.missing_keys,
2076
keys, depth=_DEFAULT_SEARCH_DEPTH)
2077
recipe = ('manual', start_set, stop_keys, key_count)
2078
body = self._serialise_search_recipe(recipe)
2079
path = self.bzrdir._path_for_remote_call(self._client)
2081
if type(key) is not str:
2083
"key %r not a plain string" % (key,))
2084
verb = 'Repository.get_parent_map'
2085
args = (path, 'include-missing:') + tuple(keys)
2087
response = self._call_with_body_bytes_expecting_body(
2089
except errors.UnknownSmartMethod:
2090
# Server does not support this method, so get the whole graph.
2091
# Worse, we have to force a disconnection, because the server now
2092
# doesn't realise it has a body on the wire to consume, so the
2093
# only way to recover is to abandon the connection.
2095
'Server is too old for fast get_parent_map, reconnecting. '
2096
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2098
# To avoid having to disconnect repeatedly, we keep track of the
2099
# fact the server doesn't understand remote methods added in 1.2.
2100
medium._remember_remote_is_before((1, 2))
2101
# Recurse just once and we should use the fallback code.
2102
return self._get_parent_map_rpc(keys)
2103
response_tuple, response_handler = response
2104
if response_tuple[0] not in ['ok']:
2105
response_handler.cancel_read_body()
2106
raise errors.UnexpectedSmartServerResponse(response_tuple)
2107
if response_tuple[0] == 'ok':
2108
coded = bz2.decompress(response_handler.read_body_bytes())
2110
# no revisions found
2112
lines = coded.split('\n')
2115
d = tuple(line.split())
2117
revision_graph[d[0]] = d[1:]
2120
if d[0].startswith('missing:'):
2122
self._unstacked_provider.note_missing_key(revid)
2124
# no parents - so give the Graph result
2126
revision_graph[d[0]] = (NULL_REVISION,)
2127
return revision_graph
2130
def get_signature_text(self, revision_id):
2131
path = self.bzrdir._path_for_remote_call(self._client)
2133
response_tuple, response_handler = self._call_expecting_body(
2134
'Repository.get_revision_signature_text', path, revision_id)
2135
except errors.UnknownSmartMethod:
2137
return self._real_repository.get_signature_text(revision_id)
2138
except errors.NoSuchRevision, err:
2139
for fallback in self._fallback_repositories:
2141
return fallback.get_signature_text(revision_id)
2142
except errors.NoSuchRevision:
2146
if response_tuple[0] != 'ok':
2147
raise errors.UnexpectedSmartServerResponse(response_tuple)
2148
return response_handler.read_body_bytes()
2151
def _get_inventory_xml(self, revision_id):
2153
return self._real_repository._get_inventory_xml(revision_id)
2155
def reconcile(self, other=None, thorough=False):
2157
return self._real_repository.reconcile(other=other, thorough=thorough)
2159
def all_revision_ids(self):
2160
path = self.bzrdir._path_for_remote_call(self._client)
2162
response_tuple, response_handler = self._call_expecting_body(
2163
"Repository.all_revision_ids", path)
2164
except errors.UnknownSmartMethod:
2166
return self._real_repository.all_revision_ids()
2167
if response_tuple != ("ok", ):
2168
raise errors.UnexpectedSmartServerResponse(response_tuple)
2169
revids = set(response_handler.read_body_bytes().splitlines())
2170
for fallback in self._fallback_repositories:
2171
revids.update(set(fallback.all_revision_ids()))
2175
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2177
return self._real_repository.get_deltas_for_revisions(revisions,
2178
specific_fileids=specific_fileids)
2181
def get_revision_delta(self, revision_id, specific_fileids=None):
2182
r = self.get_revision(revision_id)
2183
return list(self.get_deltas_for_revisions([r],
2184
specific_fileids=specific_fileids))[0]
2187
def revision_trees(self, revision_ids):
2188
inventories = self.iter_inventories(revision_ids)
2189
for inv in inventories:
2190
yield InventoryRevisionTree(self, inv, inv.revision_id)
2193
def get_revision_reconcile(self, revision_id):
2195
return self._real_repository.get_revision_reconcile(revision_id)
2198
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2200
return self._real_repository.check(revision_ids=revision_ids,
2201
callback_refs=callback_refs, check_repo=check_repo)
2203
def copy_content_into(self, destination, revision_id=None):
2204
"""Make a complete copy of the content in self into destination.
2206
This is a destructive operation! Do not use it on existing
2209
interrepo = _mod_repository.InterRepository.get(self, destination)
2210
return interrepo.copy_content(revision_id)
2212
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2213
# get a tarball of the remote repository, and copy from that into the
2216
# TODO: Maybe a progress bar while streaming the tarball?
2217
note(gettext("Copying repository content as tarball..."))
2218
tar_file = self._get_tarball('bz2')
2219
if tar_file is None:
2221
destination = to_bzrdir.create_repository()
2223
tar = tarfile.open('repository', fileobj=tar_file,
2225
tmpdir = osutils.mkdtemp()
2227
_extract_tar(tar, tmpdir)
2228
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2229
tmp_repo = tmp_bzrdir.open_repository()
2230
tmp_repo.copy_content_into(destination, revision_id)
2232
osutils.rmtree(tmpdir)
2236
# TODO: Suggestion from john: using external tar is much faster than
2237
# python's tarfile library, but it may not work on windows.
2240
def inventories(self):
2241
"""Decorate the real repository for now.
2243
In the long term a full blown network facility is needed to
2244
avoid creating a real repository object locally.
2247
return self._real_repository.inventories
2250
def pack(self, hint=None, clean_obsolete_packs=False):
2251
"""Compress the data within the repository.
2256
body = "".join([l+"\n" for l in hint])
2257
path = self.bzrdir._path_for_remote_call(self._client)
2259
response, handler = self._call_with_body_bytes_expecting_body(
2260
'Repository.pack', (path, self._lock_token,
2261
str(clean_obsolete_packs)), body)
2262
except errors.UnknownSmartMethod:
2264
return self._real_repository.pack(hint=hint,
2265
clean_obsolete_packs=clean_obsolete_packs)
2266
handler.cancel_read_body()
2267
if response != ('ok', ):
2268
raise errors.UnexpectedSmartServerResponse(response)
2271
def revisions(self):
2272
"""Decorate the real repository for now.
2274
In the long term a full blown network facility is needed.
2277
return self._real_repository.revisions
2279
def set_make_working_trees(self, new_value):
2281
new_value_str = "True"
2283
new_value_str = "False"
2284
path = self.bzrdir._path_for_remote_call(self._client)
2286
response = self._call(
2287
'Repository.set_make_working_trees', path, new_value_str)
2288
except errors.UnknownSmartMethod:
2290
self._real_repository.set_make_working_trees(new_value)
2292
if response[0] != 'ok':
2293
raise errors.UnexpectedSmartServerResponse(response)
2296
def signatures(self):
2297
"""Decorate the real repository for now.
2299
In the long term a full blown network facility is needed to avoid
2300
creating a real repository object locally.
2303
return self._real_repository.signatures
2306
def sign_revision(self, revision_id, gpg_strategy):
2307
testament = _mod_testament.Testament.from_revision(self, revision_id)
2308
plaintext = testament.as_short_text()
2309
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2313
"""Decorate the real repository for now.
2315
In the long term a full blown network facility is needed to avoid
2316
creating a real repository object locally.
2319
return self._real_repository.texts
2321
def _iter_revisions_rpc(self, revision_ids):
2322
body = "\n".join(revision_ids)
2323
path = self.bzrdir._path_for_remote_call(self._client)
2324
response_tuple, response_handler = (
2325
self._call_with_body_bytes_expecting_body(
2326
"Repository.iter_revisions", (path, ), body))
2327
if response_tuple[0] != "ok":
2328
raise errors.UnexpectedSmartServerResponse(response_tuple)
2329
serializer_format = response_tuple[1]
2330
serializer = serializer_format_registry.get(serializer_format)
2331
byte_stream = response_handler.read_streamed_body()
2332
decompressor = zlib.decompressobj()
2334
for bytes in byte_stream:
2335
chunks.append(decompressor.decompress(bytes))
2336
if decompressor.unused_data != "":
2337
chunks.append(decompressor.flush())
2338
yield serializer.read_revision_from_string("".join(chunks))
2339
unused = decompressor.unused_data
2340
decompressor = zlib.decompressobj()
2341
chunks = [decompressor.decompress(unused)]
2342
chunks.append(decompressor.flush())
2343
text = "".join(chunks)
2345
yield serializer.read_revision_from_string("".join(chunks))
2348
def get_revisions(self, revision_ids):
2349
if revision_ids is None:
2350
revision_ids = self.all_revision_ids()
2352
for rev_id in revision_ids:
2353
if not rev_id or not isinstance(rev_id, basestring):
2354
raise errors.InvalidRevisionId(
2355
revision_id=rev_id, branch=self)
2357
missing = set(revision_ids)
2359
for rev in self._iter_revisions_rpc(revision_ids):
2360
missing.remove(rev.revision_id)
2361
revs[rev.revision_id] = rev
2362
except errors.UnknownSmartMethod:
2364
return self._real_repository.get_revisions(revision_ids)
2365
for fallback in self._fallback_repositories:
2368
for revid in list(missing):
2369
# XXX JRV 2011-11-20: It would be nice if there was a
2370
# public method on Repository that could be used to query
2371
# for revision objects *without* failing completely if one
2372
# was missing. There is VersionedFileRepository._iter_revisions,
2373
# but unfortunately that's private and not provided by
2374
# all repository implementations.
2376
revs[revid] = fallback.get_revision(revid)
2377
except errors.NoSuchRevision:
2380
missing.remove(revid)
2382
raise errors.NoSuchRevision(self, list(missing)[0])
2383
return [revs[revid] for revid in revision_ids]
2385
def supports_rich_root(self):
2386
return self._format.rich_root_data
2388
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2389
def iter_reverse_revision_history(self, revision_id):
2391
return self._real_repository.iter_reverse_revision_history(revision_id)
2394
def _serializer(self):
2395
return self._format._serializer
2398
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2399
signature = gpg_strategy.sign(plaintext)
2400
self.add_signature_text(revision_id, signature)
2402
def add_signature_text(self, revision_id, signature):
2403
if self._real_repository:
2404
# If there is a real repository the write group will
2405
# be in the real repository as well, so use that:
2407
return self._real_repository.add_signature_text(
2408
revision_id, signature)
2409
path = self.bzrdir._path_for_remote_call(self._client)
2410
response, handler = self._call_with_body_bytes_expecting_body(
2411
'Repository.add_signature_text', (path, self._lock_token,
2412
revision_id) + tuple(self._write_group_tokens), signature)
2413
handler.cancel_read_body()
2415
if response[0] != 'ok':
2416
raise errors.UnexpectedSmartServerResponse(response)
2417
self._write_group_tokens = response[1:]
2419
def has_signature_for_revision_id(self, revision_id):
2420
path = self.bzrdir._path_for_remote_call(self._client)
2422
response = self._call('Repository.has_signature_for_revision_id',
2424
except errors.UnknownSmartMethod:
2426
return self._real_repository.has_signature_for_revision_id(
2428
if response[0] not in ('yes', 'no'):
2429
raise SmartProtocolError('unexpected response code %s' % (response,))
2430
if response[0] == 'yes':
2432
for fallback in self._fallback_repositories:
2433
if fallback.has_signature_for_revision_id(revision_id):
2438
def verify_revision_signature(self, revision_id, gpg_strategy):
2439
if not self.has_signature_for_revision_id(revision_id):
2440
return gpg.SIGNATURE_NOT_SIGNED, None
2441
signature = self.get_signature_text(revision_id)
2443
testament = _mod_testament.Testament.from_revision(self, revision_id)
2444
plaintext = testament.as_short_text()
2446
return gpg_strategy.verify(signature, plaintext)
2448
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2450
return self._real_repository.item_keys_introduced_by(revision_ids,
2451
_files_pb=_files_pb)
2453
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2455
return self._real_repository._find_inconsistent_revision_parents(
2458
def _check_for_inconsistent_revision_parents(self):
2460
return self._real_repository._check_for_inconsistent_revision_parents()
2462
def _make_parents_provider(self, other=None):
2463
providers = [self._unstacked_provider]
2464
if other is not None:
2465
providers.insert(0, other)
2466
return graph.StackedParentsProvider(_LazyListJoin(
2467
providers, self._fallback_repositories))
2469
def _serialise_search_recipe(self, recipe):
2470
"""Serialise a graph search recipe.
2472
:param recipe: A search recipe (start, stop, count).
2473
:return: Serialised bytes.
2475
start_keys = ' '.join(recipe[1])
2476
stop_keys = ' '.join(recipe[2])
2477
count = str(recipe[3])
2478
return '\n'.join((start_keys, stop_keys, count))
2480
def _serialise_search_result(self, search_result):
2481
parts = search_result.get_network_struct()
2482
return '\n'.join(parts)
2485
path = self.bzrdir._path_for_remote_call(self._client)
2487
response = self._call('PackRepository.autopack', path)
2488
except errors.UnknownSmartMethod:
2490
self._real_repository._pack_collection.autopack()
2493
if response[0] != 'ok':
2494
raise errors.UnexpectedSmartServerResponse(response)
2497
class RemoteStreamSink(vf_repository.StreamSink):
2499
def _insert_real(self, stream, src_format, resume_tokens):
2500
self.target_repo._ensure_real()
2501
sink = self.target_repo._real_repository._get_sink()
2502
result = sink.insert_stream(stream, src_format, resume_tokens)
2504
self.target_repo.autopack()
2507
def insert_stream(self, stream, src_format, resume_tokens):
2508
target = self.target_repo
2509
target._unstacked_provider.missing_keys.clear()
2510
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2511
if target._lock_token:
2512
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2513
lock_args = (target._lock_token or '',)
2515
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2517
client = target._client
2518
medium = client._medium
2519
path = target.bzrdir._path_for_remote_call(client)
2520
# Probe for the verb to use with an empty stream before sending the
2521
# real stream to it. We do this both to avoid the risk of sending a
2522
# large request that is then rejected, and because we don't want to
2523
# implement a way to buffer, rewind, or restart the stream.
2525
for verb, required_version in candidate_calls:
2526
if medium._is_remote_before(required_version):
2529
# We've already done the probing (and set _is_remote_before) on
2530
# a previous insert.
2533
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2535
response = client.call_with_body_stream(
2536
(verb, path, '') + lock_args, byte_stream)
2537
except errors.UnknownSmartMethod:
2538
medium._remember_remote_is_before(required_version)
2544
return self._insert_real(stream, src_format, resume_tokens)
2545
self._last_inv_record = None
2546
self._last_substream = None
2547
if required_version < (1, 19):
2548
# Remote side doesn't support inventory deltas. Wrap the stream to
2549
# make sure we don't send any. If the stream contains inventory
2550
# deltas we'll interrupt the smart insert_stream request and
2552
stream = self._stop_stream_if_inventory_delta(stream)
2553
byte_stream = smart_repo._stream_to_byte_stream(
2555
resume_tokens = ' '.join(resume_tokens)
2556
response = client.call_with_body_stream(
2557
(verb, path, resume_tokens) + lock_args, byte_stream)
2558
if response[0][0] not in ('ok', 'missing-basis'):
2559
raise errors.UnexpectedSmartServerResponse(response)
2560
if self._last_substream is not None:
2561
# The stream included an inventory-delta record, but the remote
2562
# side isn't new enough to support them. So we need to send the
2563
# rest of the stream via VFS.
2564
self.target_repo.refresh_data()
2565
return self._resume_stream_with_vfs(response, src_format)
2566
if response[0][0] == 'missing-basis':
2567
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2568
resume_tokens = tokens
2569
return resume_tokens, set(missing_keys)
2571
self.target_repo.refresh_data()
2574
def _resume_stream_with_vfs(self, response, src_format):
2575
"""Resume sending a stream via VFS, first resending the record and
2576
substream that couldn't be sent via an insert_stream verb.
2578
if response[0][0] == 'missing-basis':
2579
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2580
# Ignore missing_keys, we haven't finished inserting yet
2583
def resume_substream():
2584
# Yield the substream that was interrupted.
2585
for record in self._last_substream:
2587
self._last_substream = None
2588
def resume_stream():
2589
# Finish sending the interrupted substream
2590
yield ('inventory-deltas', resume_substream())
2591
# Then simply continue sending the rest of the stream.
2592
for substream_kind, substream in self._last_stream:
2593
yield substream_kind, substream
2594
return self._insert_real(resume_stream(), src_format, tokens)
2596
def _stop_stream_if_inventory_delta(self, stream):
2597
"""Normally this just lets the original stream pass-through unchanged.
2599
However if any 'inventory-deltas' substream occurs it will stop
2600
streaming, and store the interrupted substream and stream in
2601
self._last_substream and self._last_stream so that the stream can be
2602
resumed by _resume_stream_with_vfs.
2605
stream_iter = iter(stream)
2606
for substream_kind, substream in stream_iter:
2607
if substream_kind == 'inventory-deltas':
2608
self._last_substream = substream
2609
self._last_stream = stream_iter
2612
yield substream_kind, substream
2615
class RemoteStreamSource(vf_repository.StreamSource):
2616
"""Stream data from a remote server."""
2618
def get_stream(self, search):
2619
if (self.from_repository._fallback_repositories and
2620
self.to_format._fetch_order == 'topological'):
2621
return self._real_stream(self.from_repository, search)
2624
repos = [self.from_repository]
2630
repos.extend(repo._fallback_repositories)
2631
sources.append(repo)
2632
return self.missing_parents_chain(search, sources)
2634
def get_stream_for_missing_keys(self, missing_keys):
2635
self.from_repository._ensure_real()
2636
real_repo = self.from_repository._real_repository
2637
real_source = real_repo._get_source(self.to_format)
2638
return real_source.get_stream_for_missing_keys(missing_keys)
2640
def _real_stream(self, repo, search):
2641
"""Get a stream for search from repo.
2643
This never called RemoteStreamSource.get_stream, and is a helper
2644
for RemoteStreamSource._get_stream to allow getting a stream
2645
reliably whether fallback back because of old servers or trying
2646
to stream from a non-RemoteRepository (which the stacked support
2649
source = repo._get_source(self.to_format)
2650
if isinstance(source, RemoteStreamSource):
2652
source = repo._real_repository._get_source(self.to_format)
2653
return source.get_stream(search)
2655
def _get_stream(self, repo, search):
2656
"""Core worker to get a stream from repo for search.
2658
This is used by both get_stream and the stacking support logic. It
2659
deliberately gets a stream for repo which does not need to be
2660
self.from_repository. In the event that repo is not Remote, or
2661
cannot do a smart stream, a fallback is made to the generic
2662
repository._get_stream() interface, via self._real_stream.
2664
In the event of stacking, streams from _get_stream will not
2665
contain all the data for search - this is normal (see get_stream).
2667
:param repo: A repository.
2668
:param search: A search.
2670
# Fallbacks may be non-smart
2671
if not isinstance(repo, RemoteRepository):
2672
return self._real_stream(repo, search)
2673
client = repo._client
2674
medium = client._medium
2675
path = repo.bzrdir._path_for_remote_call(client)
2676
search_bytes = repo._serialise_search_result(search)
2677
args = (path, self.to_format.network_name())
2679
('Repository.get_stream_1.19', (1, 19)),
2680
('Repository.get_stream', (1, 13))]
2683
for verb, version in candidate_verbs:
2684
if medium._is_remote_before(version):
2687
response = repo._call_with_body_bytes_expecting_body(
2688
verb, args, search_bytes)
2689
except errors.UnknownSmartMethod:
2690
medium._remember_remote_is_before(version)
2691
except errors.UnknownErrorFromSmartServer, e:
2692
if isinstance(search, vf_search.EverythingResult):
2693
error_verb = e.error_from_smart_server.error_verb
2694
if error_verb == 'BadSearch':
2695
# Pre-2.4 servers don't support this sort of search.
2696
# XXX: perhaps falling back to VFS on BadSearch is a
2697
# good idea in general? It might provide a little bit
2698
# of protection against client-side bugs.
2699
medium._remember_remote_is_before((2, 4))
2703
response_tuple, response_handler = response
2707
return self._real_stream(repo, search)
2708
if response_tuple[0] != 'ok':
2709
raise errors.UnexpectedSmartServerResponse(response_tuple)
2710
byte_stream = response_handler.read_streamed_body()
2711
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2712
self._record_counter)
2713
if src_format.network_name() != repo._format.network_name():
2714
raise AssertionError(
2715
"Mismatched RemoteRepository and stream src %r, %r" % (
2716
src_format.network_name(), repo._format.network_name()))
2719
def missing_parents_chain(self, search, sources):
2720
"""Chain multiple streams together to handle stacking.
2722
:param search: The overall search to satisfy with streams.
2723
:param sources: A list of Repository objects to query.
2725
self.from_serialiser = self.from_repository._format._serializer
2726
self.seen_revs = set()
2727
self.referenced_revs = set()
2728
# If there are heads in the search, or the key count is > 0, we are not
2730
while not search.is_empty() and len(sources) > 1:
2731
source = sources.pop(0)
2732
stream = self._get_stream(source, search)
2733
for kind, substream in stream:
2734
if kind != 'revisions':
2735
yield kind, substream
2737
yield kind, self.missing_parents_rev_handler(substream)
2738
search = search.refine(self.seen_revs, self.referenced_revs)
2739
self.seen_revs = set()
2740
self.referenced_revs = set()
2741
if not search.is_empty():
2742
for kind, stream in self._get_stream(sources[0], search):
2745
def missing_parents_rev_handler(self, substream):
2746
for content in substream:
2747
revision_bytes = content.get_bytes_as('fulltext')
2748
revision = self.from_serialiser.read_revision_from_string(
2750
self.seen_revs.add(content.key[-1])
2751
self.referenced_revs.update(revision.parent_ids)
2755
class RemoteBranchLockableFiles(LockableFiles):
2756
"""A 'LockableFiles' implementation that talks to a smart server.
2758
This is not a public interface class.
2761
def __init__(self, bzrdir, _client):
2762
self.bzrdir = bzrdir
2763
self._client = _client
2764
self._need_find_modes = True
2765
LockableFiles.__init__(
2766
self, bzrdir.get_branch_transport(None),
2767
'lock', lockdir.LockDir)
2769
def _find_modes(self):
2770
# RemoteBranches don't let the client set the mode of control files.
2771
self._dir_mode = None
2772
self._file_mode = None
2775
class RemoteBranchFormat(branch.BranchFormat):
2777
def __init__(self, network_name=None):
2778
super(RemoteBranchFormat, self).__init__()
2779
self._matchingbzrdir = RemoteBzrDirFormat()
2780
self._matchingbzrdir.set_branch_format(self)
2781
self._custom_format = None
2782
self._network_name = network_name
2784
def __eq__(self, other):
2785
return (isinstance(other, RemoteBranchFormat) and
2786
self.__dict__ == other.__dict__)
2788
def _ensure_real(self):
2789
if self._custom_format is None:
2791
self._custom_format = branch.network_format_registry.get(
2794
raise errors.UnknownFormatError(kind='branch',
2795
format=self._network_name)
2797
def get_format_description(self):
2799
return 'Remote: ' + self._custom_format.get_format_description()
2801
def network_name(self):
2802
return self._network_name
2804
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2805
return a_bzrdir.open_branch(name=name,
2806
ignore_fallbacks=ignore_fallbacks)
2808
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
2809
# Initialisation when using a local bzrdir object, or a non-vfs init
2810
# method is not available on the server.
2811
# self._custom_format is always set - the start of initialize ensures
2813
if isinstance(a_bzrdir, RemoteBzrDir):
2814
a_bzrdir._ensure_real()
2815
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2816
name, append_revisions_only=append_revisions_only)
2818
# We assume the bzrdir is parameterised; it may not be.
2819
result = self._custom_format.initialize(a_bzrdir, name,
2820
append_revisions_only=append_revisions_only)
2821
if (isinstance(a_bzrdir, RemoteBzrDir) and
2822
not isinstance(result, RemoteBranch)):
2823
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2827
def initialize(self, a_bzrdir, name=None, repository=None,
2828
append_revisions_only=None):
2829
# 1) get the network name to use.
2830
if self._custom_format:
2831
network_name = self._custom_format.network_name()
2833
# Select the current bzrlib default and ask for that.
2834
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2835
reference_format = reference_bzrdir_format.get_branch_format()
2836
self._custom_format = reference_format
2837
network_name = reference_format.network_name()
2838
# Being asked to create on a non RemoteBzrDir:
2839
if not isinstance(a_bzrdir, RemoteBzrDir):
2840
return self._vfs_initialize(a_bzrdir, name=name,
2841
append_revisions_only=append_revisions_only)
2842
medium = a_bzrdir._client._medium
2843
if medium._is_remote_before((1, 13)):
2844
return self._vfs_initialize(a_bzrdir, name=name,
2845
append_revisions_only=append_revisions_only)
2846
# Creating on a remote bzr dir.
2847
# 2) try direct creation via RPC
2848
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2849
if name is not None:
2850
# XXX JRV20100304: Support creating colocated branches
2851
raise errors.NoColocatedBranchSupport(self)
2852
verb = 'BzrDir.create_branch'
2854
response = a_bzrdir._call(verb, path, network_name)
2855
except errors.UnknownSmartMethod:
2856
# Fallback - use vfs methods
2857
medium._remember_remote_is_before((1, 13))
2858
return self._vfs_initialize(a_bzrdir, name=name,
2859
append_revisions_only=append_revisions_only)
2860
if response[0] != 'ok':
2861
raise errors.UnexpectedSmartServerResponse(response)
2862
# Turn the response into a RemoteRepository object.
2863
format = RemoteBranchFormat(network_name=response[1])
2864
repo_format = response_tuple_to_repo_format(response[3:])
2865
repo_path = response[2]
2866
if repository is not None:
2867
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2868
url_diff = urlutils.relative_url(repository.user_url,
2871
raise AssertionError(
2872
'repository.user_url %r does not match URL from server '
2873
'response (%r + %r)'
2874
% (repository.user_url, a_bzrdir.user_url, repo_path))
2875
remote_repo = repository
2878
repo_bzrdir = a_bzrdir
2880
repo_bzrdir = RemoteBzrDir(
2881
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2883
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2884
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2885
format=format, setup_stacking=False, name=name)
2886
if append_revisions_only:
2887
remote_branch.set_append_revisions_only(append_revisions_only)
2888
# XXX: We know this is a new branch, so it must have revno 0, revid
2889
# NULL_REVISION. Creating the branch locked would make this be unable
2890
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2891
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2892
return remote_branch
2894
def make_tags(self, branch):
2896
return self._custom_format.make_tags(branch)
2898
def supports_tags(self):
2899
# Remote branches might support tags, but we won't know until we
2900
# access the real remote branch.
2902
return self._custom_format.supports_tags()
2904
def supports_stacking(self):
2906
return self._custom_format.supports_stacking()
2908
def supports_set_append_revisions_only(self):
2910
return self._custom_format.supports_set_append_revisions_only()
2912
def _use_default_local_heads_to_fetch(self):
2913
# If the branch format is a metadir format *and* its heads_to_fetch
2914
# implementation is not overridden vs the base class, we can use the
2915
# base class logic rather than use the heads_to_fetch RPC. This is
2916
# usually cheaper in terms of net round trips, as the last-revision and
2917
# tags info fetched is cached and would be fetched anyway.
2919
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2920
branch_class = self._custom_format._branch_class()
2921
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2922
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2927
class RemoteBranchStore(config.IniFileStore):
2928
"""Branch store which attempts to use HPSS calls to retrieve branch store.
2930
Note that this is specific to bzr-based formats.
2933
def __init__(self, branch):
2934
super(RemoteBranchStore, self).__init__()
2935
self.branch = branch
2937
self._real_store = None
2939
def lock_write(self, token=None):
2940
return self.branch.lock_write(token)
2943
return self.branch.unlock()
2947
# We need to be able to override the undecorated implementation
2948
self.save_without_locking()
2950
def save_without_locking(self):
2951
super(RemoteBranchStore, self).save()
2953
def external_url(self):
2954
return self.branch.user_url
2956
def _load_content(self):
2957
path = self.branch._remote_path()
2959
response, handler = self.branch._call_expecting_body(
2960
'Branch.get_config_file', path)
2961
except errors.UnknownSmartMethod:
2963
return self._real_store._load_content()
2964
if len(response) and response[0] != 'ok':
2965
raise errors.UnexpectedSmartServerResponse(response)
2966
return handler.read_body_bytes()
2968
def _save_content(self, content):
2969
path = self.branch._remote_path()
2971
response, handler = self.branch._call_with_body_bytes_expecting_body(
2972
'Branch.put_config_file', (path,
2973
self.branch._lock_token, self.branch._repo_lock_token),
2975
except errors.UnknownSmartMethod:
2977
return self._real_store._save_content(content)
2978
handler.cancel_read_body()
2979
if response != ('ok', ):
2980
raise errors.UnexpectedSmartServerResponse(response)
2982
def _ensure_real(self):
2983
self.branch._ensure_real()
2984
if self._real_store is None:
2985
self._real_store = config.BranchStore(self.branch)
2988
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2989
"""Branch stored on a server accessed by HPSS RPC.
2991
At the moment most operations are mapped down to simple file operations.
2994
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2995
_client=None, format=None, setup_stacking=True, name=None,
2996
possible_transports=None):
2997
"""Create a RemoteBranch instance.
2999
:param real_branch: An optional local implementation of the branch
3000
format, usually accessing the data via the VFS.
3001
:param _client: Private parameter for testing.
3002
:param format: A RemoteBranchFormat object, None to create one
3003
automatically. If supplied it should have a network_name already
3005
:param setup_stacking: If True make an RPC call to determine the
3006
stacked (or not) status of the branch. If False assume the branch
3008
:param name: Colocated branch name
3010
# We intentionally don't call the parent class's __init__, because it
3011
# will try to assign to self.tags, which is a property in this subclass.
3012
# And the parent's __init__ doesn't do much anyway.
3013
self.bzrdir = remote_bzrdir
3014
if _client is not None:
3015
self._client = _client
3017
self._client = remote_bzrdir._client
3018
self.repository = remote_repository
3019
if real_branch is not None:
3020
self._real_branch = real_branch
3021
# Give the remote repository the matching real repo.
3022
real_repo = self._real_branch.repository
3023
if isinstance(real_repo, RemoteRepository):
3024
real_repo._ensure_real()
3025
real_repo = real_repo._real_repository
3026
self.repository._set_real_repository(real_repo)
3027
# Give the branch the remote repository to let fast-pathing happen.
3028
self._real_branch.repository = self.repository
3030
self._real_branch = None
3031
# Fill out expected attributes of branch for bzrlib API users.
3032
self._clear_cached_state()
3033
# TODO: deprecate self.base in favor of user_url
3034
self.base = self.bzrdir.user_url
3036
self._control_files = None
3037
self._lock_mode = None
3038
self._lock_token = None
3039
self._repo_lock_token = None
3040
self._lock_count = 0
3041
self._leave_lock = False
3042
# Setup a format: note that we cannot call _ensure_real until all the
3043
# attributes above are set: This code cannot be moved higher up in this
3046
self._format = RemoteBranchFormat()
3047
if real_branch is not None:
3048
self._format._network_name = \
3049
self._real_branch._format.network_name()
3051
self._format = format
3052
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3053
# branch.open_branch method.
3054
self._real_ignore_fallbacks = not setup_stacking
3055
if not self._format._network_name:
3056
# Did not get from open_branchV2 - old server.
3058
self._format._network_name = \
3059
self._real_branch._format.network_name()
3060
self.tags = self._format.make_tags(self)
3061
# The base class init is not called, so we duplicate this:
3062
hooks = branch.Branch.hooks['open']
3065
self._is_stacked = False
3067
self._setup_stacking(possible_transports)
3069
def _setup_stacking(self, possible_transports):
3070
# configure stacking into the remote repository, by reading it from
3073
fallback_url = self.get_stacked_on_url()
3074
except (errors.NotStacked, errors.UnstackableBranchFormat,
3075
errors.UnstackableRepositoryFormat), e:
3077
self._is_stacked = True
3078
if possible_transports is None:
3079
possible_transports = []
3081
possible_transports = list(possible_transports)
3082
possible_transports.append(self.bzrdir.root_transport)
3083
self._activate_fallback_location(fallback_url,
3084
possible_transports=possible_transports)
3086
def _get_config(self):
3087
return RemoteBranchConfig(self)
3089
def _get_config_store(self):
3090
return RemoteBranchStore(self)
3092
def _get_real_transport(self):
3093
# if we try vfs access, return the real branch's vfs transport
3095
return self._real_branch._transport
3097
_transport = property(_get_real_transport)
3100
return "%s(%s)" % (self.__class__.__name__, self.base)
3104
def _ensure_real(self):
3105
"""Ensure that there is a _real_branch set.
3107
Used before calls to self._real_branch.
3109
if self._real_branch is None:
3110
if not vfs.vfs_enabled():
3111
raise AssertionError('smart server vfs must be enabled '
3112
'to use vfs implementation')
3113
self.bzrdir._ensure_real()
3114
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3115
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3116
if self.repository._real_repository is None:
3117
# Give the remote repository the matching real repo.
3118
real_repo = self._real_branch.repository
3119
if isinstance(real_repo, RemoteRepository):
3120
real_repo._ensure_real()
3121
real_repo = real_repo._real_repository
3122
self.repository._set_real_repository(real_repo)
3123
# Give the real branch the remote repository to let fast-pathing
3125
self._real_branch.repository = self.repository
3126
if self._lock_mode == 'r':
3127
self._real_branch.lock_read()
3128
elif self._lock_mode == 'w':
3129
self._real_branch.lock_write(token=self._lock_token)
3131
def _translate_error(self, err, **context):
3132
self.repository._translate_error(err, branch=self, **context)
3134
def _clear_cached_state(self):
3135
super(RemoteBranch, self)._clear_cached_state()
3136
if self._real_branch is not None:
3137
self._real_branch._clear_cached_state()
3139
def _clear_cached_state_of_remote_branch_only(self):
3140
"""Like _clear_cached_state, but doesn't clear the cache of
3143
This is useful when falling back to calling a method of
3144
self._real_branch that changes state. In that case the underlying
3145
branch changes, so we need to invalidate this RemoteBranch's cache of
3146
it. However, there's no need to invalidate the _real_branch's cache
3147
too, in fact doing so might harm performance.
3149
super(RemoteBranch, self)._clear_cached_state()
3152
def control_files(self):
3153
# Defer actually creating RemoteBranchLockableFiles until its needed,
3154
# because it triggers an _ensure_real that we otherwise might not need.
3155
if self._control_files is None:
3156
self._control_files = RemoteBranchLockableFiles(
3157
self.bzrdir, self._client)
3158
return self._control_files
3160
def _get_checkout_format(self, lightweight=False):
3163
format = RemoteBzrDirFormat()
3164
self.bzrdir._format._supply_sub_formats_to(format)
3165
format.workingtree_format = self._real_branch._get_checkout_format(
3166
lightweight=lightweight).workingtree_format
3169
return self._real_branch._get_checkout_format(lightweight=False)
3171
def get_physical_lock_status(self):
3172
"""See Branch.get_physical_lock_status()."""
3174
response = self._client.call('Branch.get_physical_lock_status',
3175
self._remote_path())
3176
except errors.UnknownSmartMethod:
3178
return self._real_branch.get_physical_lock_status()
3179
if response[0] not in ('yes', 'no'):
3180
raise errors.UnexpectedSmartServerResponse(response)
3181
return (response[0] == 'yes')
3183
def get_stacked_on_url(self):
3184
"""Get the URL this branch is stacked against.
3186
:raises NotStacked: If the branch is not stacked.
3187
:raises UnstackableBranchFormat: If the branch does not support
3189
:raises UnstackableRepositoryFormat: If the repository does not support
3193
# there may not be a repository yet, so we can't use
3194
# self._translate_error, so we can't use self._call either.
3195
response = self._client.call('Branch.get_stacked_on_url',
3196
self._remote_path())
3197
except errors.ErrorFromSmartServer, err:
3198
# there may not be a repository yet, so we can't call through
3199
# its _translate_error
3200
_translate_error(err, branch=self)
3201
except errors.UnknownSmartMethod, err:
3203
return self._real_branch.get_stacked_on_url()
3204
if response[0] != 'ok':
3205
raise errors.UnexpectedSmartServerResponse(response)
3208
def set_stacked_on_url(self, url):
3209
branch.Branch.set_stacked_on_url(self, url)
3211
self._is_stacked = False
3213
self._is_stacked = True
3215
def _vfs_get_tags_bytes(self):
3217
return self._real_branch._get_tags_bytes()
3220
def _get_tags_bytes(self):
3221
if self._tags_bytes is None:
3222
self._tags_bytes = self._get_tags_bytes_via_hpss()
3223
return self._tags_bytes
3225
def _get_tags_bytes_via_hpss(self):
3226
medium = self._client._medium
3227
if medium._is_remote_before((1, 13)):
3228
return self._vfs_get_tags_bytes()
3230
response = self._call('Branch.get_tags_bytes', self._remote_path())
3231
except errors.UnknownSmartMethod:
3232
medium._remember_remote_is_before((1, 13))
3233
return self._vfs_get_tags_bytes()
3236
def _vfs_set_tags_bytes(self, bytes):
3238
return self._real_branch._set_tags_bytes(bytes)
3240
def _set_tags_bytes(self, bytes):
3241
if self.is_locked():
3242
self._tags_bytes = bytes
3243
medium = self._client._medium
3244
if medium._is_remote_before((1, 18)):
3245
self._vfs_set_tags_bytes(bytes)
3249
self._remote_path(), self._lock_token, self._repo_lock_token)
3250
response = self._call_with_body_bytes(
3251
'Branch.set_tags_bytes', args, bytes)
3252
except errors.UnknownSmartMethod:
3253
medium._remember_remote_is_before((1, 18))
3254
self._vfs_set_tags_bytes(bytes)
3256
def lock_read(self):
3257
"""Lock the branch for read operations.
3259
:return: A bzrlib.lock.LogicalLockResult.
3261
self.repository.lock_read()
3262
if not self._lock_mode:
3263
self._note_lock('r')
3264
self._lock_mode = 'r'
3265
self._lock_count = 1
3266
if self._real_branch is not None:
3267
self._real_branch.lock_read()
3269
self._lock_count += 1
3270
return lock.LogicalLockResult(self.unlock)
3272
def _remote_lock_write(self, token):
3274
branch_token = repo_token = ''
3276
branch_token = token
3277
repo_token = self.repository.lock_write().repository_token
3278
self.repository.unlock()
3279
err_context = {'token': token}
3281
response = self._call(
3282
'Branch.lock_write', self._remote_path(), branch_token,
3283
repo_token or '', **err_context)
3284
except errors.LockContention, e:
3285
# The LockContention from the server doesn't have any
3286
# information about the lock_url. We re-raise LockContention
3287
# with valid lock_url.
3288
raise errors.LockContention('(remote lock)',
3289
self.repository.base.split('.bzr/')[0])
3290
if response[0] != 'ok':
3291
raise errors.UnexpectedSmartServerResponse(response)
3292
ok, branch_token, repo_token = response
3293
return branch_token, repo_token
3295
def lock_write(self, token=None):
3296
if not self._lock_mode:
3297
self._note_lock('w')
3298
# Lock the branch and repo in one remote call.
3299
remote_tokens = self._remote_lock_write(token)
3300
self._lock_token, self._repo_lock_token = remote_tokens
3301
if not self._lock_token:
3302
raise SmartProtocolError('Remote server did not return a token!')
3303
# Tell the self.repository object that it is locked.
3304
self.repository.lock_write(
3305
self._repo_lock_token, _skip_rpc=True)
3307
if self._real_branch is not None:
3308
self._real_branch.lock_write(token=self._lock_token)
3309
if token is not None:
3310
self._leave_lock = True
3312
self._leave_lock = False
3313
self._lock_mode = 'w'
3314
self._lock_count = 1
3315
elif self._lock_mode == 'r':
3316
raise errors.ReadOnlyError(self)
3318
if token is not None:
3319
# A token was given to lock_write, and we're relocking, so
3320
# check that the given token actually matches the one we
3322
if token != self._lock_token:
3323
raise errors.TokenMismatch(token, self._lock_token)
3324
self._lock_count += 1
3325
# Re-lock the repository too.
3326
self.repository.lock_write(self._repo_lock_token)
3327
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3329
def _unlock(self, branch_token, repo_token):
3330
err_context = {'token': str((branch_token, repo_token))}
3331
response = self._call(
3332
'Branch.unlock', self._remote_path(), branch_token,
3333
repo_token or '', **err_context)
3334
if response == ('ok',):
3336
raise errors.UnexpectedSmartServerResponse(response)
3338
@only_raises(errors.LockNotHeld, errors.LockBroken)
3341
self._lock_count -= 1
3342
if not self._lock_count:
3343
self._clear_cached_state()
3344
mode = self._lock_mode
3345
self._lock_mode = None
3346
if self._real_branch is not None:
3347
if (not self._leave_lock and mode == 'w' and
3348
self._repo_lock_token):
3349
# If this RemoteBranch will remove the physical lock
3350
# for the repository, make sure the _real_branch
3351
# doesn't do it first. (Because the _real_branch's
3352
# repository is set to be the RemoteRepository.)
3353
self._real_branch.repository.leave_lock_in_place()
3354
self._real_branch.unlock()
3356
# Only write-locked branched need to make a remote method
3357
# call to perform the unlock.
3359
if not self._lock_token:
3360
raise AssertionError('Locked, but no token!')
3361
branch_token = self._lock_token
3362
repo_token = self._repo_lock_token
3363
self._lock_token = None
3364
self._repo_lock_token = None
3365
if not self._leave_lock:
3366
self._unlock(branch_token, repo_token)
3368
self.repository.unlock()
3370
def break_lock(self):
3372
response = self._call(
3373
'Branch.break_lock', self._remote_path())
3374
except errors.UnknownSmartMethod:
3376
return self._real_branch.break_lock()
3377
if response != ('ok',):
3378
raise errors.UnexpectedSmartServerResponse(response)
3380
def leave_lock_in_place(self):
3381
if not self._lock_token:
3382
raise NotImplementedError(self.leave_lock_in_place)
3383
self._leave_lock = True
3385
def dont_leave_lock_in_place(self):
3386
if not self._lock_token:
3387
raise NotImplementedError(self.dont_leave_lock_in_place)
3388
self._leave_lock = False
3391
def get_rev_id(self, revno, history=None):
3393
return _mod_revision.NULL_REVISION
3394
last_revision_info = self.last_revision_info()
3395
ok, result = self.repository.get_rev_id_for_revno(
3396
revno, last_revision_info)
3399
missing_parent = result[1]
3400
# Either the revision named by the server is missing, or its parent
3401
# is. Call get_parent_map to determine which, so that we report a
3403
parent_map = self.repository.get_parent_map([missing_parent])
3404
if missing_parent in parent_map:
3405
missing_parent = parent_map[missing_parent]
3406
raise errors.RevisionNotPresent(missing_parent, self.repository)
3408
def _read_last_revision_info(self):
3409
response = self._call('Branch.last_revision_info', self._remote_path())
3410
if response[0] != 'ok':
3411
raise SmartProtocolError('unexpected response code %s' % (response,))
3412
revno = int(response[1])
3413
last_revision = response[2]
3414
return (revno, last_revision)
3416
def _gen_revision_history(self):
3417
"""See Branch._gen_revision_history()."""
3418
if self._is_stacked:
3420
return self._real_branch._gen_revision_history()
3421
response_tuple, response_handler = self._call_expecting_body(
3422
'Branch.revision_history', self._remote_path())
3423
if response_tuple[0] != 'ok':
3424
raise errors.UnexpectedSmartServerResponse(response_tuple)
3425
result = response_handler.read_body_bytes().split('\x00')
3430
def _remote_path(self):
3431
return self.bzrdir._path_for_remote_call(self._client)
3433
def _set_last_revision_descendant(self, revision_id, other_branch,
3434
allow_diverged=False, allow_overwrite_descendant=False):
3435
# This performs additional work to meet the hook contract; while its
3436
# undesirable, we have to synthesise the revno to call the hook, and
3437
# not calling the hook is worse as it means changes can't be prevented.
3438
# Having calculated this though, we can't just call into
3439
# set_last_revision_info as a simple call, because there is a set_rh
3440
# hook that some folk may still be using.
3441
old_revno, old_revid = self.last_revision_info()
3442
history = self._lefthand_history(revision_id)
3443
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3444
err_context = {'other_branch': other_branch}
3445
response = self._call('Branch.set_last_revision_ex',
3446
self._remote_path(), self._lock_token, self._repo_lock_token,
3447
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3449
self._clear_cached_state()
3450
if len(response) != 3 and response[0] != 'ok':
3451
raise errors.UnexpectedSmartServerResponse(response)
3452
new_revno, new_revision_id = response[1:]
3453
self._last_revision_info_cache = new_revno, new_revision_id
3454
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3455
if self._real_branch is not None:
3456
cache = new_revno, new_revision_id
3457
self._real_branch._last_revision_info_cache = cache
3459
def _set_last_revision(self, revision_id):
3460
old_revno, old_revid = self.last_revision_info()
3461
# This performs additional work to meet the hook contract; while its
3462
# undesirable, we have to synthesise the revno to call the hook, and
3463
# not calling the hook is worse as it means changes can't be prevented.
3464
# Having calculated this though, we can't just call into
3465
# set_last_revision_info as a simple call, because there is a set_rh
3466
# hook that some folk may still be using.
3467
history = self._lefthand_history(revision_id)
3468
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3469
self._clear_cached_state()
3470
response = self._call('Branch.set_last_revision',
3471
self._remote_path(), self._lock_token, self._repo_lock_token,
3473
if response != ('ok',):
3474
raise errors.UnexpectedSmartServerResponse(response)
3475
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3477
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3479
def set_revision_history(self, rev_history):
3480
"""See Branch.set_revision_history."""
3481
self._set_revision_history(rev_history)
3484
def _set_revision_history(self, rev_history):
3485
# Send just the tip revision of the history; the server will generate
3486
# the full history from that. If the revision doesn't exist in this
3487
# branch, NoSuchRevision will be raised.
3488
if rev_history == []:
3491
rev_id = rev_history[-1]
3492
self._set_last_revision(rev_id)
3493
for hook in branch.Branch.hooks['set_rh']:
3494
hook(self, rev_history)
3495
self._cache_revision_history(rev_history)
3497
def _get_parent_location(self):
3498
medium = self._client._medium
3499
if medium._is_remote_before((1, 13)):
3500
return self._vfs_get_parent_location()
3502
response = self._call('Branch.get_parent', self._remote_path())
3503
except errors.UnknownSmartMethod:
3504
medium._remember_remote_is_before((1, 13))
3505
return self._vfs_get_parent_location()
3506
if len(response) != 1:
3507
raise errors.UnexpectedSmartServerResponse(response)
3508
parent_location = response[0]
3509
if parent_location == '':
3511
return parent_location
3513
def _vfs_get_parent_location(self):
3515
return self._real_branch._get_parent_location()
3517
def _set_parent_location(self, url):
3518
medium = self._client._medium
3519
if medium._is_remote_before((1, 15)):
3520
return self._vfs_set_parent_location(url)
3522
call_url = url or ''
3523
if type(call_url) is not str:
3524
raise AssertionError('url must be a str or None (%s)' % url)
3525
response = self._call('Branch.set_parent_location',
3526
self._remote_path(), self._lock_token, self._repo_lock_token,
3528
except errors.UnknownSmartMethod:
3529
medium._remember_remote_is_before((1, 15))
3530
return self._vfs_set_parent_location(url)
3532
raise errors.UnexpectedSmartServerResponse(response)
3534
def _vfs_set_parent_location(self, url):
3536
return self._real_branch._set_parent_location(url)
3539
def pull(self, source, overwrite=False, stop_revision=None,
3541
self._clear_cached_state_of_remote_branch_only()
3543
return self._real_branch.pull(
3544
source, overwrite=overwrite, stop_revision=stop_revision,
3545
_override_hook_target=self, **kwargs)
3548
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3550
return self._real_branch.push(
3551
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3552
_override_hook_source_branch=self)
3554
def is_locked(self):
3555
return self._lock_count >= 1
3558
def revision_id_to_dotted_revno(self, revision_id):
3559
"""Given a revision id, return its dotted revno.
3561
:return: a tuple like (1,) or (400,1,3).
3564
response = self._call('Branch.revision_id_to_revno',
3565
self._remote_path(), revision_id)
3566
except errors.UnknownSmartMethod:
3568
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3569
if response[0] == 'ok':
3570
return tuple([int(x) for x in response[1:]])
3572
raise errors.UnexpectedSmartServerResponse(response)
3575
def revision_id_to_revno(self, revision_id):
3576
"""Given a revision id on the branch mainline, return its revno.
3581
response = self._call('Branch.revision_id_to_revno',
3582
self._remote_path(), revision_id)
3583
except errors.UnknownSmartMethod:
3585
return self._real_branch.revision_id_to_revno(revision_id)
3586
if response[0] == 'ok':
3587
if len(response) == 2:
3588
return int(response[1])
3589
raise NoSuchRevision(self, revision_id)
3591
raise errors.UnexpectedSmartServerResponse(response)
3594
def set_last_revision_info(self, revno, revision_id):
3595
# XXX: These should be returned by the set_last_revision_info verb
3596
old_revno, old_revid = self.last_revision_info()
3597
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3598
if not revision_id or not isinstance(revision_id, basestring):
3599
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3601
response = self._call('Branch.set_last_revision_info',
3602
self._remote_path(), self._lock_token, self._repo_lock_token,
3603
str(revno), revision_id)
3604
except errors.UnknownSmartMethod:
3606
self._clear_cached_state_of_remote_branch_only()
3607
self._real_branch.set_last_revision_info(revno, revision_id)
3608
self._last_revision_info_cache = revno, revision_id
3610
if response == ('ok',):
3611
self._clear_cached_state()
3612
self._last_revision_info_cache = revno, revision_id
3613
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3614
# Update the _real_branch's cache too.
3615
if self._real_branch is not None:
3616
cache = self._last_revision_info_cache
3617
self._real_branch._last_revision_info_cache = cache
3619
raise errors.UnexpectedSmartServerResponse(response)
3622
def generate_revision_history(self, revision_id, last_rev=None,
3624
medium = self._client._medium
3625
if not medium._is_remote_before((1, 6)):
3626
# Use a smart method for 1.6 and above servers
3628
self._set_last_revision_descendant(revision_id, other_branch,
3629
allow_diverged=True, allow_overwrite_descendant=True)
3631
except errors.UnknownSmartMethod:
3632
medium._remember_remote_is_before((1, 6))
3633
self._clear_cached_state_of_remote_branch_only()
3634
self._set_revision_history(self._lefthand_history(revision_id,
3635
last_rev=last_rev,other_branch=other_branch))
3637
def set_push_location(self, location):
3639
return self._real_branch.set_push_location(location)
3641
def heads_to_fetch(self):
3642
if self._format._use_default_local_heads_to_fetch():
3643
# We recognise this format, and its heads-to-fetch implementation
3644
# is the default one (tip + tags). In this case it's cheaper to
3645
# just use the default implementation rather than a special RPC as
3646
# the tip and tags data is cached.
3647
return branch.Branch.heads_to_fetch(self)
3648
medium = self._client._medium
3649
if medium._is_remote_before((2, 4)):
3650
return self._vfs_heads_to_fetch()
3652
return self._rpc_heads_to_fetch()
3653
except errors.UnknownSmartMethod:
3654
medium._remember_remote_is_before((2, 4))
3655
return self._vfs_heads_to_fetch()
3657
def _rpc_heads_to_fetch(self):
3658
response = self._call('Branch.heads_to_fetch', self._remote_path())
3659
if len(response) != 2:
3660
raise errors.UnexpectedSmartServerResponse(response)
3661
must_fetch, if_present_fetch = response
3662
return set(must_fetch), set(if_present_fetch)
3664
def _vfs_heads_to_fetch(self):
3666
return self._real_branch.heads_to_fetch()
3669
class RemoteConfig(object):
3670
"""A Config that reads and writes from smart verbs.
3672
It is a low-level object that considers config data to be name/value pairs
3673
that may be associated with a section. Assigning meaning to the these
3674
values is done at higher levels like bzrlib.config.TreeConfig.
3677
def get_option(self, name, section=None, default=None):
3678
"""Return the value associated with a named option.
3680
:param name: The name of the value
3681
:param section: The section the option is in (if any)
3682
:param default: The value to return if the value is not set
3683
:return: The value or default value
3686
configobj = self._get_configobj()
3689
section_obj = configobj
3692
section_obj = configobj[section]
3695
if section_obj is None:
3698
value = section_obj.get(name, default)
3699
except errors.UnknownSmartMethod:
3700
value = self._vfs_get_option(name, section, default)
3701
for hook in config.OldConfigHooks['get']:
3702
hook(self, name, value)
3705
def _response_to_configobj(self, response):
3706
if len(response[0]) and response[0][0] != 'ok':
3707
raise errors.UnexpectedSmartServerResponse(response)
3708
lines = response[1].read_body_bytes().splitlines()
3709
conf = config.ConfigObj(lines, encoding='utf-8')
3710
for hook in config.OldConfigHooks['load']:
3715
class RemoteBranchConfig(RemoteConfig):
3716
"""A RemoteConfig for Branches."""
3718
def __init__(self, branch):
3719
self._branch = branch
3721
def _get_configobj(self):
3722
path = self._branch._remote_path()
3723
response = self._branch._client.call_expecting_body(
3724
'Branch.get_config_file', path)
3725
return self._response_to_configobj(response)
3727
def set_option(self, value, name, section=None):
3728
"""Set the value associated with a named option.
3730
:param value: The value to set
3731
:param name: The name of the value to set
3732
:param section: The section the option is in (if any)
3734
medium = self._branch._client._medium
3735
if medium._is_remote_before((1, 14)):
3736
return self._vfs_set_option(value, name, section)
3737
if isinstance(value, dict):
3738
if medium._is_remote_before((2, 2)):
3739
return self._vfs_set_option(value, name, section)
3740
return self._set_config_option_dict(value, name, section)
3742
return self._set_config_option(value, name, section)
3744
def _set_config_option(self, value, name, section):
3746
path = self._branch._remote_path()
3747
response = self._branch._client.call('Branch.set_config_option',
3748
path, self._branch._lock_token, self._branch._repo_lock_token,
3749
value.encode('utf8'), name, section or '')
3750
except errors.UnknownSmartMethod:
3751
medium = self._branch._client._medium
3752
medium._remember_remote_is_before((1, 14))
3753
return self._vfs_set_option(value, name, section)
3755
raise errors.UnexpectedSmartServerResponse(response)
3757
def _serialize_option_dict(self, option_dict):
3759
for key, value in option_dict.items():
3760
if isinstance(key, unicode):
3761
key = key.encode('utf8')
3762
if isinstance(value, unicode):
3763
value = value.encode('utf8')
3764
utf8_dict[key] = value
3765
return bencode.bencode(utf8_dict)
3767
def _set_config_option_dict(self, value, name, section):
3769
path = self._branch._remote_path()
3770
serialised_dict = self._serialize_option_dict(value)
3771
response = self._branch._client.call(
3772
'Branch.set_config_option_dict',
3773
path, self._branch._lock_token, self._branch._repo_lock_token,
3774
serialised_dict, name, section or '')
3775
except errors.UnknownSmartMethod:
3776
medium = self._branch._client._medium
3777
medium._remember_remote_is_before((2, 2))
3778
return self._vfs_set_option(value, name, section)
3780
raise errors.UnexpectedSmartServerResponse(response)
3782
def _real_object(self):
3783
self._branch._ensure_real()
3784
return self._branch._real_branch
3786
def _vfs_set_option(self, value, name, section=None):
3787
return self._real_object()._get_config().set_option(
3788
value, name, section)
3791
class RemoteBzrDirConfig(RemoteConfig):
3792
"""A RemoteConfig for BzrDirs."""
3794
def __init__(self, bzrdir):
3795
self._bzrdir = bzrdir
3797
def _get_configobj(self):
3798
medium = self._bzrdir._client._medium
3799
verb = 'BzrDir.get_config_file'
3800
if medium._is_remote_before((1, 15)):
3801
raise errors.UnknownSmartMethod(verb)
3802
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3803
response = self._bzrdir._call_expecting_body(
3805
return self._response_to_configobj(response)
3807
def _vfs_get_option(self, name, section, default):
3808
return self._real_object()._get_config().get_option(
3809
name, section, default)
3811
def set_option(self, value, name, section=None):
3812
"""Set the value associated with a named option.
3814
:param value: The value to set
3815
:param name: The name of the value to set
3816
:param section: The section the option is in (if any)
3818
return self._real_object()._get_config().set_option(
3819
value, name, section)
3821
def _real_object(self):
3822
self._bzrdir._ensure_real()
3823
return self._bzrdir._real_bzrdir
3826
def _extract_tar(tar, to_dir):
3827
"""Extract all the contents of a tarfile object.
3829
A replacement for extractall, which is not present in python2.4
3832
tar.extract(tarinfo, to_dir)
3835
error_translators = registry.Registry()
3836
no_context_error_translators = registry.Registry()
3839
def _translate_error(err, **context):
3840
"""Translate an ErrorFromSmartServer into a more useful error.
3842
Possible context keys:
3850
If the error from the server doesn't match a known pattern, then
3851
UnknownErrorFromSmartServer is raised.
3855
return context[name]
3856
except KeyError, key_err:
3857
mutter('Missing key %r in context %r', key_err.args[0], context)
3860
"""Get the path from the context if present, otherwise use first error
3864
return context['path']
3865
except KeyError, key_err:
3867
return err.error_args[0]
3868
except IndexError, idx_err:
3870
'Missing key %r in context %r', key_err.args[0], context)
3874
translator = error_translators.get(err.error_verb)
3878
raise translator(err, find, get_path)
3880
translator = no_context_error_translators.get(err.error_verb)
3882
raise errors.UnknownErrorFromSmartServer(err)
3884
raise translator(err)
3887
error_translators.register('NoSuchRevision',
3888
lambda err, find, get_path: NoSuchRevision(
3889
find('branch'), err.error_args[0]))
3890
error_translators.register('nosuchrevision',
3891
lambda err, find, get_path: NoSuchRevision(
3892
find('repository'), err.error_args[0]))
3894
def _translate_nobranch_error(err, find, get_path):
3895
if len(err.error_args) >= 1:
3896
extra = err.error_args[0]
3899
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
3902
error_translators.register('nobranch', _translate_nobranch_error)
3903
error_translators.register('norepository',
3904
lambda err, find, get_path: errors.NoRepositoryPresent(
3906
error_translators.register('UnlockableTransport',
3907
lambda err, find, get_path: errors.UnlockableTransport(
3908
find('bzrdir').root_transport))
3909
error_translators.register('TokenMismatch',
3910
lambda err, find, get_path: errors.TokenMismatch(
3911
find('token'), '(remote token)'))
3912
error_translators.register('Diverged',
3913
lambda err, find, get_path: errors.DivergedBranches(
3914
find('branch'), find('other_branch')))
3915
error_translators.register('NotStacked',
3916
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
3918
def _translate_PermissionDenied(err, find, get_path):
3920
if len(err.error_args) >= 2:
3921
extra = err.error_args[1]
3924
return errors.PermissionDenied(path, extra=extra)
3926
error_translators.register('PermissionDenied', _translate_PermissionDenied)
3927
error_translators.register('ReadError',
3928
lambda err, find, get_path: errors.ReadError(get_path()))
3929
error_translators.register('NoSuchFile',
3930
lambda err, find, get_path: errors.NoSuchFile(get_path()))
3931
error_translators.register('UnsuspendableWriteGroup',
3932
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
3933
repository=find('repository')))
3934
error_translators.register('UnresumableWriteGroup',
3935
lambda err, find, get_path: errors.UnresumableWriteGroup(
3936
repository=find('repository'), write_groups=err.error_args[0],
3937
reason=err.error_args[1]))
3938
no_context_error_translators.register('IncompatibleRepositories',
3939
lambda err: errors.IncompatibleRepositories(
3940
err.error_args[0], err.error_args[1], err.error_args[2]))
3941
no_context_error_translators.register('LockContention',
3942
lambda err: errors.LockContention('(remote lock)'))
3943
no_context_error_translators.register('LockFailed',
3944
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
3945
no_context_error_translators.register('TipChangeRejected',
3946
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
3947
no_context_error_translators.register('UnstackableBranchFormat',
3948
lambda err: errors.UnstackableBranchFormat(*err.error_args))
3949
no_context_error_translators.register('UnstackableRepositoryFormat',
3950
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
3951
no_context_error_translators.register('FileExists',
3952
lambda err: errors.FileExists(err.error_args[0]))
3953
no_context_error_translators.register('DirectoryNotEmpty',
3954
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
3956
def _translate_short_readv_error(err):
3957
args = err.error_args
3958
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
3961
no_context_error_translators.register('ShortReadvError',
3962
_translate_short_readv_error)
3964
def _translate_unicode_error(err):
3965
encoding = str(err.error_args[0]) # encoding must always be a string
3966
val = err.error_args[1]
3967
start = int(err.error_args[2])
3968
end = int(err.error_args[3])
3969
reason = str(err.error_args[4]) # reason must always be a string
3970
if val.startswith('u:'):
3971
val = val[2:].decode('utf-8')
3972
elif val.startswith('s:'):
3973
val = val[2:].decode('base64')
3974
if err.error_verb == 'UnicodeDecodeError':
3975
raise UnicodeDecodeError(encoding, val, start, end, reason)
3976
elif err.error_verb == 'UnicodeEncodeError':
3977
raise UnicodeEncodeError(encoding, val, start, end, reason)
3979
no_context_error_translators.register('UnicodeEncodeError',
3980
_translate_unicode_error)
3981
no_context_error_translators.register('UnicodeDecodeError',
3982
_translate_unicode_error)
3983
no_context_error_translators.register('ReadOnlyError',
3984
lambda err: errors.TransportNotPossible('readonly transport'))
3985
no_context_error_translators.register('MemoryError',
3986
lambda err: errors.BzrError("remote server out of memory\n"
3987
"Retry non-remotely, or contact the server admin for details."))
3988
no_context_error_translators.register('RevisionNotPresent',
3989
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
3991
no_context_error_translators.register('BzrCheckError',
3992
lambda err: errors.BzrCheckError(msg=err.error_args[0]))